Channels ▼
RSS

.NET

A Silent Component Update for Internet Explorer

Source Code Accompanies This Article. Download It Now.


April, 2005: A Silent Component Update for Internet Explorer

Customizing your browser

Zuoliu Ding is a software engineer in California. He can be contacted at zqxd@ hotmail.com.


A component is an object that encapsulates data/code and provides a set of specified services built in a DLL, OCX, or EXE module. An Internet Explorer (IE) component can be a COM/ActiveX object, such as a browser toolbar. Among other things, it attaches itself to each IE browser running instance, giving you access to the IE object model with its properties, methods, and events. Such a component is also called a "Browser Helper Object" (BHO) in IE parlay. Putting security flaws aside, IE is a widely used web browser that's often considered a platform for Internet application development. For example, an independent service provider may want to create a private-labeled toolbar, such as the Google or MSN toolbar. This isn't difficult if you have knowledge of Microsoft browser programming. However, as soon as you create it, you'll probably have to consider updating it for bug fixes or feature enhancements. Consequently, you might prefer a "silent" toolbar update that doesn't require user attention or interaction.

In the article "Silent Application Update" (DDJ, November 2004), I presented a technique to silently update standalone executables. For IE component updating, the basic process is similar. When a browser is in use (runtime), the IE component monitors its new version from the web server. If updates are available, it downloads and installs the necessary files. Yet for components, more issues need to be addressed in terms of the installation, registration, and other update perspectives.

In this article, I present a silent update technique for IE components. For illustrative purposes, I present a demo called "CMP Media toolbar" (available electronically; see "Resource Center," page 5). As soon as silent updates occur in the browser, this toolbar not only updates itself, but incorporates a pop-up blocker for the IE browser.

Microsoft's XP Service Pack 2 includes its own integrated pop-up blocker. While my pop-up blocker is far from a commercial product, it still blocks enough for this demo. The CMP toolbar is written in Visual C++ and ATL/WTL, mainly targeting the XP/NT family with IE 5 (and above). The current IE 6 was released in 2001 and updated slightly with two Service Packs. Microsoft isn't planning a new IE version until Longhorn, expected in 2006 (http://zdnet.com/2100-1104_2-5304259.html). That said, the basic IE object model and its API should live longer, along with the technique in this article.

Working with CMP Toolbar

The package ieUpDemo.zip (available electronically) contains cmpbar.dll, tbconfig.xml, and install.exe in the Demo folder. Copy these files and run install.exe to set up the first version of CMP toolbar (Figure 1). On the top-right is the toolbar context menu with the CMP Media toolbar checked. Click the CMP Media button to see a dropdown menu. To uninstall, click the Uninstall menu item or use Add or Remove Programs.

To update, click the Toolbar Options menu item to open the Update page (Figure 2) in the Options dialog, where you check the Enable Silent Update box. Two radio buttons let you either update from an uploaded site or localhost. For now, choose the test site and click on OK to close. After navigating for a while, a message window pops up telling you that the CMP toolbar 2.0 has updated successfully.

You can see the new toolbar appearance the next time you open an IE browser. It is equipped with a Search button and a Simplified Pop-up Blocker (Figure 3), indicating that the toolbar has been updated. To verify that my pop-up blocker is in place, turn off SP2's pop-up blocker (in the Tools menu) to avoid a fight (assuming you're using XP SP2). Try a pop-up test site on the dropdown. Before a pop-up pops, you'll see a message box with Block and Allow choices. If you block it, the toolbar's Simplified Pop-up Blocker button flashes. This tells you that the downloaded _blocker.dll is functioning and has been added to your Demo folder. Then, the smiley-face icon is updated on the CMP Media button, and as expected, you find two new icon files in the Demo folder. The remaining two files are a discarded cmpbar.dll and tbconfig.xml appended with the ".old" extension.

How does it happen? Six relevant files are provided in the ieupdate folder. What you need to do is upload them to the localhost. The result should look like Example 1. As you see, five files with the suffix "2" are mentioned (this suffix is for convenience, but is not required).

Example 2 shows ieupdate.xml, where the server should provide all necessary information for updates. In this XML, I identify each download file by its tag name, file version number on the server, file size, and download location. A location should correspond to a server URL where the file is uploaded, as compared to Example 1. For each file, I also give a command set that is executed sequentially in updates. Once you set up the localhost, you can test the CMP toolbar update locally. Choose the second option in Figure 2, press OK to close, and navigate a while until you see the prompt.

Update Procedure

In my previous article, I suggested two phases to update executables—File Download and File Update—in two sessions. In the IE component update, however, the operations can be completed in one application session; see Figure 4.

As usual, you have to create a thread for processing without blocking navigations in IE. First, you download ieupdate.xml, read it into memory, and download the designated files. The second part is for the File Update phase. To fulfill an update specific to each downloaded file, I defined the given commands as Remove, Copy, and Register, corresponding to three functions in the program. Table 1 lists the command sets for each file.

These commands are straightforward. Since the toolbar module cmpbar.dll is running, you must remove it first, then replace it by copying from cmpbar2.dll. Copy and register the new component _blocker2.dll. The configuration tbconfig.xml is in use, too. For safety, I still remove it and copy it. Finally, copy two new icons for the main logo button.

If all commands work well, I save the new version information and prompt the user about changes. I use the Registry as storage and the test results might look like Figure 5. However, if the update fails in any step, I must clean the updates and return to the previous toolbar files.

Implementations

Listing One presents the function UpdateBrowserBar(), which retrieves the update location and starts a thread to run ThreadUpdateBrowserBar(). ThreadUpdateBrowserBar() is just a code mapping to Figure 4. In this function, I create a CAppInfo object to parse XML contents from StreamDownload() and call DownloadFiles(), which detects new versions, downloads files, and checks integrity.

Among the three commands, Remove is worthy of mention because "removing" is not the real meaning of the function implementation in RemoveFiles(). There is no way to remove a DLL component in runtime. You should keep that DLL at hand, in case of update failure, so that you can return to it. The API function MoveFileEx() is ideal for this job. I use it to rename a running DLL in XP without affecting its current process. Listing Two shows this implementation with two MoveFileEx() calls. First, I rename the cmpbar.dll with the extension ".old." Then, with the help of flag MOVEFILE_DELAY_UNTIL_REBOOT, cmpbar.dll.old is not deleted until the next reboot.

You have seen cmpbar.dll.old and tbconfig.xml.old after a successful update. But if either of MoveFileEx() fails, I call the helper ResumeFiles() to get ".old" files back and call DeleteFiles() to clean all ".new" files, because each downloaded file is appended with a ".new" extension. The implementations of Copy and Register commands (omitted here) are easier to understand in the source code.

You may wonder when and where UpdateBrowserBar() is triggered in Listing One. The answer is, any time in a navigation event, preferably at the end of a web page download. Also, in an IE session, you should invoke an update only once. So I start UpdateBrowserBar() in the DocumentComplete() event handler (see DISPID_DOCUMENTCOMPLETE in Listing Three) in the browser's dispatch method Invoke(). I use the member variable m_bUpdated to prevent a second call to UpdateBrowserBar().

Now imagine two scenarios: In one situation, users could close an IE instance when the toolbar is halfway through updating; and the other situation is when a site navigation initializes a silent update, but the site shoots out a pop-up. Because a pop-up can be an IE instance as well, it starts its own update thread, overlapped its parent update. The outcome would be unexpected, possibly causing a downloading abort or the corruption of an existing file.

To protect an update, I added the static variable s_bUpdating. If it is True, UpdateBrowserBar() never starts a work thread again; otherwise, I set and reset s_bUpdating before and after an update in ThreadUpdateBrowserBar(). Moreover, consider if the aforementioned scenarios happened in Invoke() (see DISPID_ONQUIT and DISPID_NEWWINDOW2 in Listing Three). In the OnQuit() event, when m_bUpdating is True, I wait every three seconds until s_bUpdating becomes False. In the NewWindow2() event, as long as m_bUpdating is True, I ruthlessly kill any pop-ups by returning VARIANT_TRUE for the Cancel parameter. A sophisticated choice is using a synchronization object like a mutex.

Bridge and Template

Can this sample be copied to a project in practice? The answer is both yes and no. Remember that an update occurs to generate the next version of software. In fact, you have no way to predict the future product behavior in the current update code. For instance, you may want to replace the previous GUID in a component or combine another DLL for content filtering. Sometimes, a change is not limited to the modules present. Even though I make efforts to design the update portion to be as general as possible, it might not apply in the future.

But this will not cause a problem, so long as you leave an update stub in your main component—you can always maintain a silent feature in your update. Here, the "stub" means the least updated operations: downloading, renaming, and copying the module itself. For instance, assume you have the CMP toolbar 1.0 with such a stub update. I suggest an intermediate update CMP toolbar 1.5, working like a bridge between the old CMP toolbar 1.0 and the new toolbar 2.0, as in Figure 6. Version 1.5 has the same toolbar portion as 1.0, but contains different update code that should meet your current requirements. The 1.0 stub update furtively brings the toolbar 1.5, which eventually ignites a real toolbar update from a different download location.

In terms of the toolbar DLL, have you compared two cmpbar.dll files before and after an update? Surprisingly, they are the same. The download does happen to cmpbar.dll, but it really acts on both Versions 1.0 and 2.0. It just depends on whether the value of DemoVersion in tbconfig.xml has been updated. Consequently, taking advantage of this reusability saves me a build for the code package. In the real world, a newly downloaded DLL is certainly different than its predecessor.

However, this strategy might be useful to make updates flexible. I tried to design cmpbar.dll to be a kind of a toolbar template without sticking to one client. Most of its buttons and menus are configurable in XML, managed by the CToolbarConfig object (see TbConfig.cpp/.h). Although it sounds unrealistic, you can update the CMP toolbar to a News toolbar just by copying tbconfig.xml and two icons from the Demo/NewsCfg folder to replace the current ones. Example 3 shows tbconfig.xml, and Figure 7 shows the News toolbar in action.

While taking a close look at your News toolbar immediately after copying, you would find that the display name remains "CMP Media" rather than "News" in the toolbar context menu (top-right). And the same problem occurs with the Add or Remove Programs applet. Recall in Table 1, the commands Remove and Copy are used to update cmpbar.dll. Now they are neither sufficient nor appropriate: You need to have "Register" to make the "News" name available in the registry for other IE and system components. To do this manually, double-click install.exe, which performs registering. Hence, the names will be corrected in both the context menu and the applet.

Conclusion

The silent component update for the IE browser I've presented here is not limited to IE and can be extended to other components. In complex software development projects, you often have to update both executables and components, which requires more consideration in update architecture and coordination.

The CMP toolbar targets the XP/NT family because, in Win98/ME, MoveFileEx() fails to rename a component during runtime. To solve this, you can create a batch file containing the update commands and make a silent update in the next reboot. Finally, the CMP toolbar and Simplified Pop-up Blocker are built with Microsoft Visual C++ 7.0 and WTL 7.0. Make sure that you have installed WTL 7.0 in the Microsoft Visual Studio IDE. Before compiling, I recommend moving $(VCInstallDir)PlatformSDK\include to the first line in the VC++ Include path settings and choosing MinDependency for the Release build.

DDJ



Listing One

/*--------------------------------------------------------------
Toolbar Update Main Procedure
--------------------------------------------------------------*/
void UpdateBrowserBar(DWORD dw)
{
   if (CBarBand::s_bUpdating) return;
   static CString str = dw==2? _UpdateLocal:_UpdateSite;
   DWORD dwThreadId;
   HANDLE h = ::CreateThread(NULL, 0, ThreadUpdateBrowserBar, 
                                  (LPVOID)&str, 0, &dwThreadId);
   CloseHandle(h);
}
DWORD WINAPI ThreadUpdateBrowserBar(void *p)
{
   CString sInfo = *((CString*)p);
   if (sInfo =="") Return;

   CAppInfo AppInfo;
   CBarBand::s_bUpdating = TRUE;

   sInfo = StreamDownload(sInfo);
   if (sInfo.IsEmpty()) Return;

   if (!AppInfo.LoadUpdateXml(sInfo)) Return;
   if (!DownloadFiles(&AppInfo)) Return;
   if (!RemoveFiles(&AppInfo)) Return;
   if (!CopyFiles(&AppInfo)) Return;
   if (!RegisterFiles(&AppInfo)) Return;

   AppInfo.SaveAllFileVersions();
   PromptUpdatedInfo();

   CBarBand::s_bUpdating = FALSE;
   Return;
}
Back to article


Listing Two
/*--------------------------------------------------------------
Helper function RemoveFiles() 
--------------------------------------------------------------*/
BOOL RemoveFiles(CAppInfo* pAppInfo)
{
   CString sFile;
   for (int i=0; i<APPINFO_COUNT; i++)
   {
      if (-1 !=pAppInfo->GetUpdate(szFiles[i], Command).
         Find("Remove"))
      {
         sFile = GetModulePath()+szFiles[i];
         DeleteFile(sFile+_OLD);   // if any

         if (!MoveFileEx(sFile, sFile+_OLD, NULL) ||
             !MoveFileEx(sFile+_OLD, NULL, MOVEFILE_DELAY_UNTIL_REBOOT))
         {  
            ResumeFiles(pAppInfo);
            DeleteFiles(_NEW);
            return FALSE;
         }
      }
   }
   return TRUE;
}
void DeleteFiles(CString sType)
{
   CString sFile;
   for (int i=0; i<APPINFO_COUNT; i++)
   {
      sFile = GetModulePath()+szFiles[i] +sType;
      DeleteFile(sFile);
   }
}
void ResumeFiles(CAppInfo* pAppInfo)
{
   CString sFile;
   for (int i=0; i<APPINFO_COUNT; i++)
   {
      if (-1 !=pAppInfo->GetUpdate(szFiles[i], Command).
         Find("Remove"))
      {
         sFile = GetModulePath()+szFiles[i];
         CopyFile(sFile+_OLD, sFile, FALSE);
      }
   }
}
Back to article


Listing Three
/*--------------------------------------------------------------
IDispatch Method in Toolbar Update
--------------------------------------------------------------*/
STDMETHODIMP CBarBand::Invoke(DISPID dispidMember, REFIID, LCID, WORD,
                       DISPPARAMS* pDispParams, VARIANT*, EXCEPINFO*, UINT*)
{
   if (!pDispParams) return E_INVALIDARG;
   switch (dispidMember)
   {
      case DISPID_DOCUMENTCOMPLETE:
      {
         if (!m_bUpdated) 
         {
            DWORD dw =0;
            ReadRegDW("SUpdate", dw);

            if (dw==1 || dw==2) 
            {
               UpdateBrowserBar(dw);
               m_bUpdated =TRUE;
            }
         }
         break;
      }
      case DISPID_NEWWINDOW2:
      {
         if (CBarBand::s_bUpdating)
            *pDispParams->rgvarg[0].pboolVal= VARIANT_TRUE;   
         break;
      }
      case DISPID_ONQUIT:
      {
         while (s_bUpdating) Sleep(3000);
         Disconnect();
         break;
      }
   }
   return S_OK;
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video