Dr. GUI
October 13, 1997
. . . to Dr. GUI's not-quite-first not-quite-real nonquestion column. (The question and answer columns will continue to appear in Developer Network News, printed and online.) Now that his patients will have even more appointments with the good doctor, he promises to travel far and wide—sometimes even to Building 7—in hopes of finding interesting tidbits to share.
For the next few weeks, Dr. GUI would like to share information he learned in a place beyond even Building 7—specifically, at the recent Microsoft Professional Developers Conference in San Diego.
There are many topics the good doctor is thinking of addressing—Dynamic HMTL, Windows CE, data access, and so on—but it seems that the most important for this week is how to tweak your ActiveX controls so they work great with the latest release of Internet Explorer.
Internet Explorer 4.0 brings some new rules for writing controls so that they'll run optimally. Older controls will still work, but you will find that a few changes will help your controls—and your user's system—run much better. Two of the changes are particularly important: using at least apartment-model threading, and supporting windowless operation.
Why should you bother optimizing your control for Internet Explorer 4.0? Two reasons: First, Internet Explorer 4.0 is likely to be one of the most popular browsers ever. Second, the Internet Explorer 4.0 Dynamic HTML control will be used on the Active Desktop and in a wide variety of applications, including e-mail, HTML Help, the Visual Studio IDE, and many third-party applications. In other words, a lot of users will feel the improvements you make.
If that isn't enough, Microsoft is encouraging developers to take advantage of the Dynamic HTML control to provide UI for many applications, both traditional and Web-based. The easy programmability and rich display capabilities of Dynamic HTML make using the control very attractive. Dr. GUI's looking forward to using DHTML for everything from dialog boxes to full-blown applications.
So if you want the widest possible market for your controls, you'll want to make sure they work well with Internet Explorer 4.0. Luckily, doing so requires only minor surgery. Your controls won't even need general anesthesia—just a shot of Novocaine.
Does your control show symptoms of needing Internet Explorer 4.0 optimization? Check your control against Dr. GUI's symptom list for symptoms of gotta-optimize-itis:
If your control shows one or more of these symptoms, it has gotta-optimize-itis. Fortunately, this disease is never fatal and relatively easily cured by using one or more of the techniques below.
As you know, there are three major ways to write ActiveX controls: you can use C++ with MFC, C++ with ATL, or Visual Basic. What you may not know is that in Internet Explorer 4.0 any Java Bean is automatically an ActiveX control, so you can use Java as well—even for visual controls. (Other tools, such as Delphi, support the writing of ActiveX controls, too.)
For the best control over your controls and the fastest controls, Dr. GUI prescribes ATL 2.0, the Active Template Library that ships with Visual C++ 5.0. For easier, slightly smaller controls, you can use MFC (but your control will require that the MFC DLL be installed on the user's machine). Visual Basic is easy to write, but also requires a large DLL and doesn't give the fine-grained control of the C++ technologies. Java controls require the Java VM (included with Internet Explorer), are somewhat slow, and don't provide fine-grained control. (It's hard to write good-looking controls with AWT, but between AFC and J/Direct you can write anything you want in Java.)
Since this article is about optimization, we'll focus on ATL and MFC controls.
This is a biggie, because you can really degrade your user's performance if you ignore it. So it's very important to make sure that your control uses apartment model multi-threading. Many older controls are single-threaded; such controls cause huge performance problems when multiple instances of the control are run through multiple instances of the Internet Explorer 4.0 Web browser control, which is most of the time nowadays. (This was also an issue with Internet Explorer 3.0, but it wasn't as noticeable because it wasn't as common to run multiple copies of the browser.)
By default, controls created with MFC (version 4.2 and later) and with the latest Visual Basic 5.0 update are automatically apartment-model multithreaded. If you're using ATL, you're really in luck: you get a choice when creating your control. (Dr. GUI likes to be able to control his controls.) If you have controls built with older versions of MFC or Visual Basic, it's an easy matter to update to the latest version and to rebuild the controls—that should be the majority of the work you'll need to do to update your control. And don't forget to mark your control as apartment-model multithreaded in the registry.
But Dr. GUI has to warn you about one very important other issue: while the MFC, ATL, and Visual Basic run times are protected against the re-entrancy problems caused by multithreaded access, you have to protect your global and static data. To do this, the good doctor prescribes appropriate synchronization techniques (semaphores and the like) to protect all global and static data by insuring that it can only be operated on by one thread's scalpel at a time.
The next change you should make to MFC and ATL controls is to make them windowless—so they do their drawing on the container's device context rather than on their own. Unfortunately, in the current release, Visual Basic controls cannot be windowless. So to create a windowless control, you'll have to use MFC or ATL.
Making the control windowless has several advantages besides the fact that windowless controls are faster. The most important for Internet Explorer 4.0 is that windowless controls can have multimedia filter and transition effects applied to them. If your control has its own window, Internet Explorer 4.0 cannot apply the cool multimedia effects to the control—this ties the hands of folks who use your controls. The good doctor knows you don't want to do that.
In addition to all that, windowless controls can be non-rectangular and transparent—and Internet Explorer 4.0 can manipulate their z-order (which control is on top). If you remember the "Mr. ActiveX Eggplant Head" demo using transparent, irregularly shaped controls, you'll have some idea of the power of windowless controls. In addition to being efficient, they're cool!
Internet Explorer 4.0 also changes how controls are activated: they're not made in-place active unless they're visible. This means that if you need a window to be a message target, you'll need to create your own; you won't get the normal in-place active window unless you're visible. If you try to access the window when it doesn't exist, you'll end up getting egg on your face as you use a NULL handle.
In Internet Explorer 3.0, there wasn't a really clean way for a control to tell the browser whether the control was ready to be scripted or not. Programmers came up with a number of kludges to work around this, but none was AMA-approved.
In Internet Explorer 4.0, there's a standard way to give detailed information about when your control is ready: just implement the DISPID_READYSTATE automation property and call the container's IPropertyNotifySink::OnChanged when the ready state changes. Note that if you implement ready state you must be sure to properly notify the container. If you don't, the window.onLoad script method will never be called. It's not called until all objects that implement ready state have indicated that they're completely ready.
In Internet Explorer 3.0, there was a "page cache" of four pages. That means that your ActiveX controls (and Java applets) would be kept alive as the user navigated up to three pages away. This was handy because it allowed the user to navigate back and still have the control present in the same state it was when the user navigated away. For instance, if the control contained something the user was editing, the contents of the control would be the same when the user came back.
This caching caused performance problems, however, so it was eliminated in Internet Explorer 4.0. That means that controls suffer amnesia—they lose their memory—if you navigate even one page away. But Dr. GUI does have a prescription that will take minimal work on your part and work even better than before.
When your control starts, call IPersistHistory::LoadHistory to see if the browser control has any data for you to load. If successful, it returns an IStream interface from which you can read.
When your control is about to be destroyed because the browser is moving to a different page, call IPersistHistory::SaveHistory to get an IStream interface to which you can write the data you need to save.
The data written by IPersistHistory will be kept for the same browser session only, regardless of how many pages away the user navigates. (This is a big advantage over an arbitrary page limit.) But don't write huge amounts of data using this mechanism: the total system limit for all pages in all instances of the browser is only 2 MB. For large data, such as images and database query results, use the cache API to put the data file in the cache and write only the identifier of the object using IPersistHistory.
It's easy for your control to access the Dynamic HTML objects on the Web page: just call IOleClientSite::GetContainer to get access to your container object, then use QueryInterface to get a pointer to the IHTMLDocument2 interface. At that point, you can access and even modify every object on your Web page.
This can be an attractive alternative to exposing your source code as script. (If you ship script, you're exposing yourself to examination by any code doctor.) Note that it's easier to prototype and debug your code in script, then translate the script into interface calls.
It's very important to sign your controls, even if they aren't safe. (Don't mark them as safe if they're not safe, though!) Signing and marking are two different things: signing is just saying that you wrote a control, marking it is further asserting that the control is safe to use in any Web page. Think of writing a control as like painting a fine painting: what artist wouldn't sign their work? Browsers can be set to simply ignore unsigned controls, so it's doubly important to sign yours. (In fact, both high and medium security settings ignore unsigned controls entirely.)
Before you mark your control as safe (by implementing IObjectSafety), make sure it really is safe. (Dr. GUI knows you know how important being safe is nowadays.) There are two types of safety: safe for initialization and safe for scripting. Safe for initialization means that there is no possible initialization input stream that would cause the control to damage a user's machine. (Could an input value, for instance, cause an array to be over indexed?) Safe for scripting means that there's no possible script command or combination of script commands that would cause the control to damage a user's machine. (You especially have to watch out for invalid parameter values.)
Although this is not a complete list, here are some behaviors that are almost always unsafe:
If you can't mark your control as safe, sign it anyway so the user can use it from a proper security zone.
The best way to deliver controls and other programs to the user's machine will be using the OSD (Open Software Description) standard. OSD is an XML vocabulary designed by Marimba and Microsoft and supported by Netscape, Lotus, BackWeb, and more.) You can deliver OSD content as a separate file or as a part of a set of Content Definition Format (CDF) tags. OSD will support either push or pull for distribution.
For Internet Explorer 3.0, you'll need to author <OBJECT> tags that point to a .CAB file that has an embedded .INF file. For Internet Explorer 4.0, you can embed the OSD file (and the INF) in the CAB, or reference a CDF file. Note that this new method automatically provides uninstall functionality, as well. (The good doctor wonders if that was enough TLAs for you.)
It's important that you not rely on the user's machine having DLLs (such as MFC, the C run time, the Visual Basic run time, or the ATL run time) available. It's a simple matter to add a few lines of code to your INF file to cause the right CAB file to be downloaded from Microsoft if the user's machine doesn't have the DLL they need. In this way, only users who don't already have the DLL will have to spend time getting it.
Dr. GUI notes with regret (and no small amount of anger and frustration) that it's increasingly common for extraordinarily rude programs to pop up a dialog in your face while you're in the midst of doing something else. What's worse, these programs even steal the keyboard focus, meaning that the characters you're typing into your application end up being processed by the dialog. Dr. GUI joins Miss Manners in denouncing such caddish behavior on the part of programs (or, for that matter, on the part of people).
In an environment where any page can be used as the desktop, you may find that your control is actually placed on the user's desktop. Because of this, it's very important that you never bring up a dialog asynchronously. Only bring up dialogs in response to user actions. This is good design anyway (ask Dr. Manners), but it's especially important for controls on the desktop.
Instead of bringing up a dialog, change the visible state of the control or even of the taskbar. For instance, if the server is unavailable for your stock ticker control, just put a message that says "Server unavailable" in the control rather than interrupting the user with an unnecessary and annoying dialog. If the condition is very important, you can play a sound to alert the user. But don't just pop up a dialog box and steal activation and focus from the application the user is using.
One of Dr. GUI's favorite underused Internet-related interfaces is the IBindHost interface, which is very handy when you need to get data from somewhere else on the Web. Check out this powerful interface the next time you're writing a control that needs to get data over the Web.
The good doctor has also noticed a number of common problems that show up when using Internet Explorer version 3.0 controls with version 4.0. Double-check these common problems:
Dr. GUI finds that debugging controls when the Active Desktop is enabled is a special challenge since the IDE debugger doesn't get to load the browser—it's already loaded. This makes it difficult to set breakpoints in controls. The simplest option is just to disable the Active Desktop when you're debugging. If you'd rather not do that, you can attempt to attach to the Active Desktop process or use Task Manager to kill the Active Desktop (Explorer shell) process. If you kill the Active Desktop, there are a number of ways to restart it, including double-clicking on the desktop, pressing Ctrl+Shift+Esc, and starting it with the task manager.
Dr. GUI notices that there are some great benefits to be gained by writing your ActiveX controls in a way that's optimal for Internet Explorer 4.0. First, your control will be a good Internet Explorer 4.0 control. Second, it'll work well in new user interfaces constructed using the Dynamic HTML control, so it'll get use far beyond the Web.
Of the optimizations, the most important to implement are apartment-model awareness and windowlessness (trying saying that five times fast!). You also need to think about security issues all the time—when you're designing, coding, testing, and preparing to sign your control. Don't put off thinking about security until the end.
If you do these optimizations, Dr. GUI promises that you'll take maximum advantage of all your containers, including Internet Explorer.
Thanks to Dave Massy for the useful talk at PDC that provided material for this column. If you have questions, be sure to see the Internet Client SDK for more information.
Your Turn Whaddya think? Any comments? Do you have any topics you'd like to see the good doctor address? You can write the Doc at drgui@microsoft.com. Although the good doctor's surgery schedule precludes individual replies, Dr. GUI does read and consider all mail, even Java flames.