Alok Sinha, Don Clore, and Dale Phurrough
Alok Sinha is the development manager for MSN Core and UI. Don Clore was part of the team that wrote the MSN Program Viewer and many of the ActiveX controls used by MSN. Dale Phurrough is the lead Web development engineer for MSN.
The Microsoft Network™ (MSN) 1.0 was launched with much fanfare in August 1995, and it has become moderately successful since then. It was a completely proprietary service—from the Mediaview-based client user interface to the proprietary chat, BBS, mail, and directory servers to the communication protocols. It was the right approach in the 1993-95 time frame. However, within six months of shipping MSN, it became apparent that MSN needed to adopt and embrace the Internet. As a result, there were widespread changes—from new Internet protocol-based servers to HTML and ActiveX™-based content.
In this article, we'll present a case study of MSN 2.0. We'll describe the key components of The Microsoft Network, lessons learned in authoring HTML-based content for the Web, and techniques for writing really lightweight ActiveX controls and containers. We are not really interested in extolling the virtues of MSN or the infrastructure on which it is built, but rather to share with you the components of a Web-based online service. We believe that many of you are trying to publish content on the Web and will find this case study interesting.
As we work through this case study, we'll present an overview of the MSN infrastructure and show the techniques we developed for writing lightweight ActiveX controls. We'll also illustrate how to write ActiveX control containers and demonstrate how to use the latest HTML and scripting developments to create a cutting-edge Web page (and hence, a cutting-edge Web site).
The latest MSN (codenamed Hartford) shipped in November 1996. The battle cry of the new network was "made for the medium," that medium being the Internet, which brought content to most users through torturously slow 14.4 or 28.8 Kbps modems. New servers were designed and implemented on top of Microsoft Internet Information Server (IIS). The new client was based on Microsoft® Internet Explorer 3.0 (IE 3.0). Figure 1 shows the big picture.
Figure 1 MSN Infrastructure
On the client side, an MSN member uses the Program Viewer (PV) to view MSN content. The PV is essentially an ActiveX container containing IE 3.0 as an ActiveX control. This allows the PV to display MSN-specific toolbars and navigation bars and still fully utilize the IE 3.0 browser engine (MSHTML.DLL). We will present more details on the PV later. An MSN member can use Microsoft Comic Chat (Cchat.exe) for chatting on the Internet, Microsoft Exchange client for sending and receiving mail, and Microsoft Internet News client for
participating in MSN and Usenet bulletin boards (BBS) or forums.
On the server side, the MSN online service uses the Microsoft Commercial Internet System News Server for hosting multiple forums and Microsoft Commercial Internet Chat Server for hosting chat rooms. In addition, much of MSN's content is created dynamically using the Active Server Pages (ASP) and Active Server components. MSN utilizes the Microsoft Membership Broker System (MBS) to provide a single logon across all MSN services. MBS is used to uniquely identify users, validate their access rights, and generate billing events. For now, MSN continues to use the proprietary MSN Mail Server. This mail system will be replaced with Internet Mail Server (IMS) in the near future.
Figure 2 shows the OnStage (http://onstage.msn.com) area of MSN within the MSN Program Viewer. The PV is an ActiveX container hosting the IE 3.0 ActiveX control. This allows the PV to show HTML-based Web content within its frame.
Figure 2 MSN Program Viewer
Figure 3 shows the interfaces the PV supports to enable it to host the IE 3.0 ActiveX control. In addition to hosting Internet Explorer, the PV also displays a simple navigation toolbar that allows the MSN members to navigate to core areas of MSN without typing any URLs. When the user clicks on the core menu items, the PV sends a URL for the site to the browser via the IWebBrowser interface implemented by the WebBrowser ActiveX control. Of course, the user can click on a toolbar icon and expose the Internet toolbar (see Figure 4). The Internet toolbar allows the user to type in the URL of any site, print the current page, and so on. By default, the Internet toolbar is hidden when a member is navigating within the MSN domain.
Figure 3 Interfaces Supported by Program Viewer
Interface | Mandatory? |
IOleClientSite | Yes |
IOleContainer | Yes |
IServiceProvider | No |
IAdviseSink | Yes |
IOleInPlaceFrame | Yes |
IOleInPlaceSite | Yes |
IOleCommandTarget | Yes |
Figure 4 Internet Toolbar
From a pure content perspective, MSN is composed of many "shows." Each show is constructed with HTML, VBScript, JavaScript, Active Server Pages, and ActiveX controls embedded in HTML and Active Server Objects. Later, we will delve deeper into the creation of the OnStage page (http://onstage.msn.com/default.asp) to provide an insight into how it was created. (Note: some of the URLs mentioned are only accessible to MSN subscribers.—Ed.)
From a Web-site perspective, each show resides at a distinct URL (for example, http://mint.msn.com). Each site is replicated onto multiple IIS servers with new users connecting to a particular IIS server as dictated by the round-robin scheme of Domain Name Service (DNS). This allows load balancing among the IIS servers and improves the availability of the site. Usually, each site is maintained in nonoverlapping directory structures with an IIS virtual root pointing to the top of each directory structure (such as C:\mint). This system allows content in one show to be propagated independent of other shows.
How do we propagate content for many shows (or Web sites) to many IIS servers? Each show can have one or more pages that are updated at a varying frequency. For example, news-related shows (http://www.msnbc.com) can be propagated several times a day whereas WebZines like V-Style (http://www.v-style.msn.com) may be propagated once every couple of weeks. In each case, the Web design engineer creates HTML or ASP pages after discussions with the producer and program manager of the show. Each page may contain contributions from one or more digital artists (bitmaps and animations), sound engineers (RealAudio or WAV files), or copyeditors.
Finally, the media elements and all the Web pages are checked into Microsoft Visual SourceSafe™ version control system and an email notice is sent to the Build team. The Build team gets the content out of the Visual SourceSafe system, sets it up on test IIS servers, and notifies the Test team. The test engineers verify the sanity of the pages and component-test ActiveX controls, database connectivity, or any other show-specific technology. As bugs are found, they are fixed until the new content is ready for public consumption. At that time, we run a simple Perl-based script to change internal server names to final URLs—for example, to change a reference from http://test
server/mint/default.asp to http://www.mint.msn.com/default.asp.
The content is replicated to each IIS server configured to handle the site (Mint may be replicated to multiple IIS servers). We use Microsoft Commercial Content Replication System (CRS) for replication of content. One final pass is made at the publicly exposed IIS servers to ensure correct setup and propagation of content. The Data Center staff continuously monitors the health of the servers—from routers to Web servers—using the many system management tools at their disposal.
Figure 5 presents an overview of servers used by MSN. MSN Data Center uses both IIS 1.0 and IIS 2.0 to host Web-based content. In order to increase availability time, a given MSN site is replicated to at least two IIS servers. This way, administrators can use DNS to switch users between the servers hosting the site's content. A given IIS server can host multiple sites via virtual roots and virtual server mechanisms.
Figure 5 Internet Servers in MSN Data Center
Server Type | Platform | Server Name |
Web Server | Windows NT 3.51 | Microsoft Internet Information Server 1.0 (IIS) |
Web Server | Windows NT 4.0 | Microsoft Internet Information Server 2.0 (IIS) |
Chat Server | Windows NT 4.0 | Microsoft Commercial Internet ChatServer (ICS) |
BBS Server | Windows NT 4.0 | Microsoft Commercial Internet News Server (INS) |
Personalization or User Property Database | Windows NT 3.51 | Microsoft Commercial Membership Personalization System (MPS) |
Security | Windows NT 3.51/4.0 | Microsoft Membership Broker System (MBS) |
Active Server Scripting and Objects | Windows NT 4.0 | Microsoft Active Server Platform (Denali) |
Authentication and Billing Servers (A&B) | Windows NT 3.51 | MSN Billing and Authentication Servers |
Performance of the Web servers is measured periodically. The best measures of IIS server performance are the processor load, disk usage load, and network throughput. These are measured through the performance monitors exposed by Windows NT®. Each Web server is also monitored to ensure that all the services are functioning. This is accomplished by testers using a custom client-side tool that downloads certain pages from the Web server via the HTTP protocol (using the WinINet APIs).
Web servers hosting free content (http://www.msn.com) are placed in front of the subscription wall, while MSN premium content (also referred to as shows) is placed behind the subscription wall. The subscription wall is created by checking user identities and allowing passage only to MSN members. For this purpose, Microsoft Membership Broker System is used.
The Communicate area of MSN uses Comic Chat as a standalone chat client. Quite a few shows use the Microsoft Chat OCX (MSChat.OCX) to showcase show-specific chat rooms embedded within the ambiance of their HTML pages. These and other chat clients communicate with the Microsoft Commercial Internet Chat Servers (ICS). The ICS server exposes the Microsoft Internet Chat (MIC) communication protocol, which is a superset of the Internet Relay Chat version 2 (IRC2) protocol. Hence, it can communicate with existing IRC2 clients as well as with new MIC-based clients. The MIC protocol allows text and data transmission in the chat channels, while IRC2 simply allows text transmission. Each ICS server can support up to 4096 users. ICS servers can be scaled to handle larger numbers of users
by connecting up to 255 ICS servers to create a chat-server collection, all responding to a single URL (http://chat.msn.com). When more than two chat servers are collected under a single URL, their physical interconnection must not have closed loops—they must be connected in a spanning tree. Figure 6 illustrates the ICS server architecture.
In Figure 6, five ICS chat servers are arranged in a network and they communicate with each other through chat "portals." The ICS server in the center has been expanded to show its internal workings. ICS runs as an extension of the IIS service and can be managed by the IIS Internet Service Manager (ISM). ICS interacts with Membership Broker (formerly codenamed Sicily) for user account authentication. The clients connect to the ICS server over port 6667.
Figure 6 ICS Architecture
Chat applications (such as MSChat.OCX) communicate with the ICS server using ChatSock APIs and ChatSock COM interfaces. These APIs and interfaces hide the details of the MIC protocol. However, it's useful to think of the ICS server as your information router for Internet games since MIC allows data transmission. The advantage of using the ICS server as an information router is that you don't have to write a distributed (multiple servers) and scalable (from hundreds to thousands of users) server. Non-chat applications such as games and conferencing can use Server Extensions (Channel Service API), which allows development of Channel Services DLLs that reside on the chat server and extend it. Additionally, you can use ICS Active Server Components (ASC) to support the development of server-side scripts that enable the presentation of user interfaces for chatting, lists of active chats and members, and logs of chat histories on Web pages.
Figure 7 shows how ICS supports creating a chat server network for scalability. Notice that, while all users believe they are communicating with a single chat server (http://chat.msn.com), in reality they are connected to multiple chat servers. The chat servers relay information about each other through portals.
Figure 7 Creating a Chat Server Network for Scalability
MSN uses the Microsoft Commercial Internet System News Server (INS) to host MSN-specific and Usenet newsgroups. The INS server is based on the standard Network News Transport Protocol (NNTP), which allows it to communicate with other Internet news servers. MSN members can use the Microsoft Internet News client application to participate in newsgroups.
The news server allows the overall news service to scale easily as more users and newsgroups are added to the system. This is achieved by creating one master news server and one or more slave news servers. Users typically connect to the slave servers for normal browsing—subscribing to newsgroups, downloading articles, and reading the articles. However, when a user posts an article, the master server is needed for creating a unique ID for this article. Additionally, the master server handles incoming as well as outgoing NNTP-based newsfeeds. Figure 8 shows the overall architecture of INS.
Figure 8 INS Architecture
Notice that INS supports NNTPS, which is a security extension of the NNTP protocol. The news server works with multiple authentication protocols, including anonymous guest account-level security, clear-text (AUTHINFO), and Windows Challenge/Response (NTLM). A security support provider interface (SSPI) provides the interface between the NNTP service and the NTLM protocol. For encryption support, the news server supports the Secure Sockets Layer (SSL) protocol. NNTP-based clients connect to INS over port 119, whereas SSL-based connection is made over port 563.
The news server stores individual news articles in a large Windows NTFS disk space, including descriptive data about articles, newsgroups, and history, in a set of internal files or data structures. The file store needs to be large enough to hold data as well as the overhead associated with each article, which is 540 bytes per article plus one byte for every 128 articles. An average article takes about 2KB on the hard disk. This is further illustrated in Figure 9. Notice that the news server itself runs as an IIS service and can be managed by the ISM. Additionally, you can set up the news server to send news updates to Microsoft Index Server (part of IIS 2.0 on Windows NT 4.0) to create indexes for searching individual news articles.
Figure 9 News Server Components
When the Web was young, site designers added dynamic features to their sites via CGI-based programs. A common example is displaying the number of visitors for the given site. However, this approach is inefficient because a process is launched each time a user request (via HTTP) comes to the Web server. IIS 1.0 introduced the concept of Internet Server API (ISAPI) DLLs, which added dynamic behavior to Web pages. This is more efficient because it starts off a new thread (as opposed to a new process) for each new request in whose context the ISAPI DLL is executed. While this is an efficient solution, writing good ISAPI DLLs is not easy for the average Web site builder.
The Active Server solution (codenamed Denali) was designed based on a script engine implemented as an ISAPI DLL. The script engine executes the ASP file when a user request comes to the Web server. This has the dual benefit of being more efficient than writing CGI programs and of allowing the average Web site builder to add dynamic features to Web pages. This is further illustrated by Figure 10.
Figure 10 Active Server Model
The Active Server Pages contain regular HTML as well as scripts written in VBScript, JavaScript, Perl, and so on. The scripts can be executed on the server or at the client site. The additional powerful feature of the Active Server engine is that COM objects can be activated via the scripts, which in turn can provide dynamic data such as database lookups.
At the highest level, Active Server scripts take one of several forms. The script can use commands and expressions inside <% and %> tags. The script can also use procedures and functions written in VBScript or some other scripting language and formatted inside <SCRIPT> and
</SCRIPT> tags.
This ASP example shows how you can set a variable (iFruit =4) and later use it to dynamically change the content of the page.
% iFruits = 4 % There are %= iFruits % on the table.
The browser shows:
There are 4 fruits on the table.
Figure 11 demonstrates a script that executes on the server. The function Echo is embedded in the ASP file. This function is called by the expression "% Call Echo %", and uses the Request Active Server intrinsic object to show the content of an HTTP request to the server. The values are dynamically written into HTML (via Response.Write) when the page is executed.
Figure 11 A Script that Executes on the Server
ASP File:
<SCRIPT LANGUAGE = VBScript RUNAT=Server> Sub Echo Response.Write _ "<TR><TD>Name</TD><TD>Value</TD></TR>" Set Params = Request.QueryParameters For Each p in Params Response.Write "<TR><TD>" & p & "</TD><TD>" & _ Params(p) & "</TD></TR>" Next End Sub </SCRIPT> <HTML> <TITLE> Echo using ActiveX Server Framework and VBScript </TITLE> Echo using ActiveX Server Framework and VBScript <P> <TABLE> <% Call Echo %> </TABLE> </HTML>
Browser Shows:
Echo using ActiveX Server Framework and VBScript Name Value AUTH_TYPE CONTENT_LENGTH 0 CONTENT_TYPE GATEWAY_INTERFACE CGI/1.1 LOGON_USER PATH_INFO /devtesting/joeuser/test.asp PATH_TRANSLATED C:\InetPub\wwwroot\Dev\joeuser\test.asp QUERY_STRING REMOTE_ADDR 157.55.210.31 REMOTE_HOST 157.55.210.31 REQUEST_METHOD GET SCRIPT_MAP SCRIPT_NAME /devtesting/joeuser/test.asp SERVER_NAME joeuser SERVER_PORT 80 SERVER_PORT_SECURE 0 SERVER_PROTOCOL HTTP/1.0 SERVER_SOFTWARE Microsoft-IIS/2.0 URL HTTP_ACCEPT image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* HTTP_ACCEPT_LANGUAGE en HTTP_CONNECTION Keep-Alive HTTP_HOST joeuser HTTP_UA_PIXELS 800x600 HTTP_UA_COLOR color16 HTTP_UA_OS Windows NT HTTP_UA_CPU x86 HTTP_USER_AGENT Mozilla/2.0 (compatible; MSIE 3.0; Windows NT) HTTP_COOKIE ASPSESSIONID=BHKQIBCIPXKEKWIR
While it is beyond the scope of this article to present a full ASP programming guide, Figure 12 summarizes the key features. (If you want further details, Active Server white papers can be downloaded from http://www.microsoft.com/iis/NoFrames/Evaluating/Guides/Whitepapers/aspwp.htm).
Figure 12 ASP Programming Features
Feature | Details |
Variable Types | int, strings, single, double, Date, long, null, arrays, and so on |
Control Flow | If..then..else..endif, Do..Loop, For..Next, For Each..Next, While..Wend |
Comments | REM |
Script Support | Vbscript, JavaScript, Perl, and so on |
Form Support | Elements of an HTML form can be determined by a script using the Request object |
Cookie Support | Cookie can be easily extracted or set by using Request.Cookie or Response.Set methods |
Dynamic Response | Can be created using the Response object |
User Login | Can be forced using the Require.Authentication method |
Object support | Objects can be created at runtime on the server and their methods invoked from ASP file |
MSN Web sites make extensive use of the Member Personalization System (MPS), which has three key elements. The user property database allows a site to maintain user preferences such as background color. The SMTP Mail (Smail) Active Server object allows an ASP file to send mail via an SMTP mail server (although the mail server is not part of MPS itself). Finally, the Voting Control Active Server object allows the Web designer to collect user votes and save them in a database.
Undoubtedly, the user property database is the most important (and most used) feature of MPS. Today, it is used across Microsoft sites, including www.msn.com, www.msnbc.com, and many others. The database is actually implemented by maintaining user preferences (such as background color = blue and stock-of-interest = msft) in a flat file, so there is no need to define a schema for the database. Another way to visualize the database is to imagine one INI file per user maintained on the Web site—with the INI file containing key = value pairs. The architecture of MPS is shown in Figure 13.
Figure 13 MPS Architecture
The user files are maintained in an NTFS-based Windows NT file server. In this system, each user is identified by a globally unique 32-byte ID (GUID). The GUID is created when the user visits a site for the first time, and it's stowed away in a cookie on the user's machine. After the first visit, an ASP can pull up user preferences by simply asking for values corresponding to the GUID.
The following script segment shows how to employ the user property database to store a user's favorite sport. Ask the User is an HTML page for entering favorite sports. When the user submits
information, the browser sends a string like "sname=baseball", appended after the URL, to the server (for example, www.sports.com/submit.asp?sname=baseball). (Note: Code is wrapped for publishing reasons onlyæEd.)
<HTML> <BODY> <form method=post action="submit.asp"> What is your favorite sport? <input name="sname"><p> <input type=submit> </form> </BODY></HTML>
The next example is an ASP file (submit.asp) called to save the sport value. Notice that the property database Active Server object is created first using the CreateObject method of the server object. Next, the designer can stow away the favorite sports value (contained in sname) in the database simply by calling the item method. Request is an intrinsic object of Active Server that allows the parsing of post results.
<HTML> <BODY> <% set upd = Server.CreateObject ("MPS.PropertyDatabase") %> <% upd.item("sname") = Request("sname") %> Thanks for telling us about your favorite sport %= Request("sname") %.<p> </BODY></HTML>
Any other ASP file can pull up the favorite sport value. The value can be accessed via the item method or directly by specifying the property name (sname).
<HTML> <BODY> <% set upd = Server.CreateObject ("MPS.PropertyDatabase") %> Welcome! We know that your favorite sports is <% = upd("sname") %>.<p> </BODY></HTML>
As mentioned earlier, the user property database is stored on an NTFS file store. There are many configurations supported as Figure 14 illustrates. The simplest configuration is to have IIS host the MPS system as well as its own file store. The second and more common configuration shows multiple Web servers (installed with MPS) using the same user property database store. The third configuration uses secondary file stores as backup for a primary file store. Finally, the fourth configuration is meant for large sites where there are multiple user-property databases, each with its own backup.
Figure 14 Configuring MPS
The Smail object allows you to send mail directly from the ASP as long as an SMTP server is configured. The following script fragment shows the Smail object inaction. The script sends mail to MoneyMan@dollars.com, a message from a user pointed to by the emailname variable.
<% REM SendMail (from, to, subject, body) %> <%set Sm = Server.CreateObject("MPS.SendMail")%> <% Sm.SendMail ("MoneyMan@dollars.com", Request("emailname"), "Thank You!", "We appreciate your feedback." & Chr(13) & "Thanks for taking the time to answer our survey.") %>
The Voting control allows the Web designer to collect votes and create a tally. The object has the following methods:
The script segment in Figure 15 shows how to use the Voting control.
Figure 15 Using the Voting Control
<% set vt = Server.CreateObject("MPS.Vote") %> <% REM If no vote processed, show form for users to vote. %> <% if (Request("Content_Length") = 0 ) then %> <form action="doc2.asp" method="post"> Vote for Top American sport: <input type=hidden name=vote value="on"> <input type=radio name=sport value="football">Football <input type=radio name=sport value="basketball">Basketball <input type=radio name=sport value="baseball">Baseball <input type=submit value="Vote"> </form> <% REM If content has been posted, process it and display vote %> <% else %> <% if vt.Open("vote", "guest", "") = TRUE then %> <% ballotresult = vt.SetBallotName("Sport") %> <% voteresult = vt.Submit("Sport", Request("sport")) %> <% = vt.GetVote("Sport") %> <p> <% else %> The Voting component is not set up correctly. <% end if %> <% end if %>
MSN online service provides content that's available to anyone (such as http://www.msn.com or http://carpoint.msn.com). However, a significant part of MSN's content is available only to members, including email, chat, and BBS. At its basic level, content can be segregated into "free" and "members only" areas. A finer level of control is provided by adding "pay per session," "premium package," or "pay when you download a file" types of services. MSN uses the Membership Broker System (MBS) for authentication and billing services. MBS has four key features. First, single-user ID/password allows MSN members to log in once during a session and then use all services (Web, email, chat, BBS, and so on) under the same credentials.
The second feature is distributed security. MBS does not transmit passwords over the network, and allows application servers protected by MBS to be physically dispersed on the Internet. While most MSN services (such as email and chat) exist on the same secure LAN, this feature allows MSN to host content on a remote server.
Third is integrated security. MBS is tightly integrated with Windows NT security systems. While it uses Windows NT access controls and user/group accounts to enforce secure access to server services, it extends the Windows NT security by using MBS Tokens. On the server side, MBS has been integrated into IIS 2.0, and the chat, news, and mail servers. On the client side, it has been incorporated into various applications including Internet Explorer, Netscape Navigator (using a new Winsock client), Microsoft Chat Client, Comic Chat, and the Internet News client.
Fourth are billing events. MBS provides billing APIs that can be used to generate bills against MSN members. The MBS system itself does not provide a billing system, but it allows billing events to be generated that can be imported into MSN's internal billing system using MBS billing APIs.
The MBS system is implemented by using components at three sites: users computers, application servers, and an MBS back-end. On the client side are Distributed Password Authentication (DPA)-enabled clients such as Internet Explorer 3.0. DPA is implemented as an SSPI package. With Netscape Navigator, the user's machine must be configured to use an authentication proxy, which in turn communicates with the SSPI. Figure 16 shows Internet Explorer and Netscape Navigator using the DPA package. Notice that the credentials (user ID and password) are cached. Thus, users who run more than one application during a session do not need to log on more than once.
Figure 16 MBS Client-server components
When a user attempts to access an MBS-enabled Web server, the server sends back an HTTP 401 (Access Denied) error, which prompts the client to engage the DPA package via the SSPI. This leads to negotiate/challenge/response over the Internet, although the user's password is never transmitted. On the application server end, a broker is used via the SSPI interface. The broker in turn validates the user's access by connecting to the MBS back-end, which consists of special servers that authenticate the user and determine access rights. The Web chat, email, BBS, and address book servers are all MBS-enabled.
There are a number of good software frameworks available for writing ActiveX controls. MFC, Active Template Library (ATL), and the basectl sample code that ships with the Microsoft ActiveX SDK all do a great job of shielding the author from the nitty-gritty details of the OLE compound document interfaces that comprise the plumbing of an ActiveX control.
When the OLE Controls specification first became available, OLE Controls were conceived as a superset of an in-process, in-place, editing document server. The amount of work required to write one of these controls without reusing some code to do the base OLE compound document work was a barrier to entry for the author who wanted to focus on the functionality specific to the control.
With the release of IE 3.0, the definition of OLE Controls (now called ActiveX controls) has been simplified and streamlined such that a control need only support the IUnknown interface to run in Internet Explorer. The new component categories functionality in OLE was also defined such that a control can provide information to the container about what categories of interfaces it supports without the container having to QueryInterface for each one.
Obviously, a control that only supports IUnknown can't do very much. In this article, we are going to provide a sample control that supports IUnknown, a very minimal subset of IOleObject, and IDispatch. It's a windowless, invisible control with an OLE Automation interface that's callable from VBScript.
Why would anyone want to write such a control? There are a lot of good reasons for not doing it this way. The MFC, ATL, and basectl codebases are all very well tested and stable, and they provide a lot of reusable functionality. Also, these frameworks provide 1994-style ActiveX controls that can run in containers other than Internet Explorer (like Microsoft Visual Basic®), utilizing all the work that's been done by Microsoft and other companies creating application development tools that can reuse ActiveX controls.
But sometimes Internet ActiveX development has a compelling need for a very specific functionality that can't be achieved with HTML and VBScript alone, so a very specific ActiveX control is needed. Such a control frequently needs to be downloaded quickly over 14.4Kbps phone lines. (A good rule of thumb—at 14.4Kbps, it takes one second to download each kilobyte of data.) Using the technique we describe here, very simple ActiveX controls can be created that range in size from 13KB to 18KB, depending on how much functionality is needed.
An ActiveX control is an OLE in-process server. This means it's a DLL with a few specific entry points. It needs to export DllGetClassObject, DllCanUnloadNow, DllRegisterServer, and DllUnregisterServer. These have all been documented elsewhere, so we won't describe their functionality here.
We need a class factory object to hand out instances of our control. Controls running in Internet Explorer must be apartment thread-safe (see "Give ActiveX-based Web Pages a Boost with the Apartment Threading Model," MSJ, February 1997). This means that the class factory methods must be reentrant.
Probably the only interesting thing to note about the sample is that we're keeping a global object count for the DLL to correctly implement DllCanUnloadNow. This variable must be accessed in a thread-safe way—either bracket it with a critical section as we do, or use InterlockedIncrement/Decrement. The other interesting thing the class factory does is implement CreateInstance to hand out instances of the control. Not much to note here, except to see how the class factory maintains reentrancy.
The control itself is just a C++ class (CLiteCtl) with a reference count. It implements IUnknown, IOleObject, and IDispatch. It forms the vtable for IUnknown/IOleObject with its own C++ class definition, and implements a contained C++ class (CDispatch) to implement IDispatch. CDispatch has no reference count of its own; it delegates IUnknown interface requests (like AddRef and Release) back to the main object. CDispatch is a dual automation interface (see the MSJ, February 1997, "Visual Programmer" column for a complete discussion of dual interfaces). This means its vtable must have the first seven methods of IDispatch, with the dispinterface methods and properties tacked onto the end of the vtable. Making CDispatch a separate subobject easily achieves this end. We also could have used C++ multiple inheritance to achieve the same result by deriving CLiteCtl from both IOleObject and CDispatch. There's no particular reason why we did it one way or the other, so if you feel strongly about it, do it your own way.
AddRef and Release are obvious and need no comment. QueryInterface just returns the CLiteCtl pointer when IOleObject and IUnknown are asked for; when IDispatch
is asked for it returns the address of the contained CDispatch object.
CLiteCtl's implementation of IOleObject is extremely minimalist, so much so that we defined macros in the header file to return inline E_NOTIMPL or S_OK, respectively, for most of the interface methods. This implementation of IOleObject supports no verbs, so it really supports only the methods shown in Figure 17.
Figure 17 Methods Supported by IOleObject
STDMETHOD(GetUserClassID)(CLSID *pClsid) { *pClsid = CLSID_CLiteCtl return S_OK; } STDMETHOD(GetUserType)(DWORD dwFormOfType, LPOLESTR *pszUserType) { return OLE_S_USEREG; } STDMETHOD(GetMiscStatus)(DWORD dwAspect, DWORD *pdwStatus) { return OLEMISC_INVISIBLEATRUNTIME | OLEMISC_SETCLIENTSITEFIRST; } STDMETHODIMP CLitCtl::SetClientSite(LPOLECLIENTSITE pCS) { if (pCS) { m_pCS = pCS; m_pCS->AddRef(); } else { if (m_pCS) { m_pCS->Release(); m_pCS = 0; } } return S_OK; // always return S_OK. }
As you can see, the implementation is pretty slim. In fact, there was no compelling need to support IOleObject at all. We really just wanted to show how to implement SetClientSite. You can use the IOleClientSite pointer obtained this way to do interesting things, as we will show later. Feel free to omit IOleObject entirely if you like.
This is the most interesting of the interfaces the control supports, and is probably the entire reason you want to do a control at all: you want to provide some programmable functionality to a bit of VBScript running in a Web page.
These days, creating a dual automation interface is trivial. OLE (specifically, oleaut32.dll) does almost all the work for you. You do need to create an ODL file that describes your automation interface, compile it to a TypeLib (TLB) file using mktyplb.exe (or MIDL.EXE, the Microsoft RPC compiler), and include the TLB file as a resource in your DLL. When your control is instantiated, it needs to load this TypeLib and get a pointer to it. Further calls to IDispatch methods will be delegated to this TypeLib. OLE will find the correct offsets to your automation methods and call them for you. Note the conversion of ANSI strings to Unicode before calling OLE methods. OLE is all Unicode internally, both on Windows NT and Windows® 95 (see Figure 18).
Figure 18 Converting ANSI Strings to Unicode
HRESULT CDispatch::Init(VOID) { HRESULT hr; CHAR ach[MAX_PATH]; WCHAR awch[MAX_PATH]; LPTYPELIB pTL = 0; hr = ::LoadRegTypeLib( TLID_LiteCtl, 1, 0, LOCALE_SYSTEM_DEFAULT, &pTL); if (hr) { // get the name of this binary. _SIDE_ASSERTE(::GetModuleFileName( g_hInst, ach, (sizeof ach / sizeof ach[0])—1)); // convert the ANSI string to wide _SIDE_ASSERTE(::MultiByteToWideChar( CP_ACP, 0, ach, -1, awch, NElems(awch))); // load the typelib from the binary hr = ::LoadTypeLib(awch, &pTL); if (hr) goto Cleanup; } _ASSERTE(pTL); hr = pTL->GetTypeInfoOfGuid(DIID_DMSNGateway, &m_pTI); Cleanup: if (pTL) pTL->Release(); return hr; } STDMETHODIMP CDispatch::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo ** ppTI) { if (!ppTI) return E_POINTER; *ppTI = 0; if (0 != itinfo) return TYPE_E_ELEMENTNOTFOUND; _ASSERT(m_pTI); m_pTI->AddRef(); *ppTI = m_pTI; return S_OK; } STDMETHODIMP CDispatch::GetIDsOfNames( REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgdispid) { _ASSERTE(m_pTI); return ::DispGetIDsOfNames(m_pTI, rgszNames, cNames, rgdispid); } STDMETHODIMP CDispatch::Invoke( DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, UINT * puArgErr) { if (IID_NULL != riid) return DISP_E_UNKNOWNINTERFACE; _ASSERTE(m_pTI); // clear the errorinfo object. ::SetErrorInfo(0, 0); return m_pTI->Invoke( this, dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); }
Couldn't be much easier, right? You can add whatever properties and methods you like by putting them in the ODL file and adding corresponding C++ members to the end of the CDispatch class definition. A few examples of properties and methods can be found in the sample code that is available from any of the sources listed on page 5.
We said you could do interesting things, didn't we? You can actually get hold of the browser's IWebBrowserApp or IWebBrowser interface and manipulate Internet Explorer directly. You can resize it, cause it to navigate to another Web site, turn off the toolbar, status bar, menu, and so on. Note that this functionality is also exposed to VBScript, so it's questionable as to when this is useful inside an ActiveX control. We note it here mostly as an illuminating educational exercise!
To implement something like this, start from IOleClientSite and QueryInterface for IServiceProvider. From IServiceProvider, QueryService for SContainerDispatch and IDispatch (SID_ScontainerDispatch is defined in DOCOBJ.H that ships with the Microsoft ActiveX SDK, found at http://microsoft.com/intdev/sdk/). From the returned IDispatch, ask for an IDispatch-valued property called Script. From the Script IDispatch, ask for an IDispatch-valued property called Explorer. The VBScript/HTML equivalent of this is to reference the window.explorer property in a VBScript event handler.
The automation object you end up with is actually the automation wrapper for an instance of IWebBrowser or IWebBrowserApp (depending on whether the browser is Internet Explorer or another container hosting the IE WebBrowser ActiveX control). The header file EXDISP.H from the Internet SDK shows all the properties and methods. The sample code shows how to access the ToolBar property to turn the toolbar on and off. Note that this is all late-bound stuff, meaning that you actually use the string "ToolBar" to return a DISPID that you pass to the browser's IDispatch::Invoke when you access the property.
The other thing that's interesting about having the IOleClientSite pointer is that you need it to get hold of the IOleInPlaceFrame interface by calling QueryInterface for IOleInPlaceSite and then calling IOleInPlaceSite::GetWindowContext. This is interesting because it's often useful to write a control that nominally has no UI of its own, but that pops up a message box or a modal dialog to do something when a method is invoked. If you do this, you must call the container's IOleInPlaceFrame::EnableModeless(FALSE) before bringing up the dialog, and IOleInPlaceFrame::EnableModeless(TRUE) after the dialog goes away. Doing this is very important; it's necessary to let the container know that you're going into a PeekMessage loop (which is how MessageBox and DialogBox work), and that the container's message pump won't be processing all the messages until after you call EnableModeless(TRUE).
You will need to implement component categories for your control to mark it as "safe for scripting" so scripters can use your automation interface, and "safe for initializing" if you intend for your control to be able to load property values from PARAM statements in the HTML code. This component categories code will also need to go into your DllRegisterServer implementation
CLiteCtl does not implement a conventional UI or fire events. For a more full-featured control with its own user interface, you're going to have to implement a lot of interfaces—probably IViewObjectEx, possibly IOleInPlaceObject and IOleInPlaceActiveObject, and maybe IOleControl itself. To fire events, you'll need to implement IConnectionPointContainer, IConnectionPoint, IEnumConnectionPoint, and IProvideClassInfo (or IProvideClassInfo2 or 3). Obviously, at some point it makes much more sense to just use a framework that implements this for you. In particular, the Active Template Library can produce full-featured controls that are extremely small, so it's worthwhile looking in this direction. So what do you have now? You've got an OLE inproc server that's very similar to an automation server. You could use it in Visual Basic by calling CreateObject. However, Internet Explorer can only load controls via the <OBJECT> tag, so it must be a control. Note that the control could still function without the IOleObject or the IDispatch implementation. It only needs the IUnknown implementation; everything else is up to the implementor.
IE 3.0 is incredibly extensible. You can use Web pages to turn off the menu, the toolbar, or the status bar, and you can embed custom ActiveX controls that serve as navigational UI elements. Both scripts running in the page and ActiveX controls embedded in the HTML can drive the browser, causing it to navigate to other sites. Sometimes, however, you want to completely change the look and feel of the browser. It may become necessary to write your own Windows application that hosts the WebBrowser ActiveX control. This is an ActiveX control that is exported by SHDOCVW.DLL, one of the principal IE 3.0 components. The CLSID (CLSID_WebBrowser) can be found in exdisp.h from the Microsoft ActiveX SDK.
To host this control, you're going to have to use Visual Basic, MFC, or roll your own. We're going to show you the do-it-yourself approach. The sample code that accompanies this article (CTR.EXE) builds a simple Windows-based application that hosts the WebBrowser ActiveX control. Its purpose is to show you the OLE compound document plumbing required to make such an application work. This is a very specialized ActiveX control container, one that hosts only one control, the WebBrowser ActiveX control. This allows us to make some simplifying assumptions in the implementation. Figure 19 shows the list of interfaces we have implemented.
Figure 19 Interfaces Implemented in CTR
Name | Mandatory? |
IOleClientSite | Yes |
IOleContainer | Yes |
IAdviseSink | Yes |
IOleInPlaceSite | Yes |
IOleInPlaceFrame | Yes |
IOleCommandTarget | Yes |
IServiceProvider | No |
IOleClientSite is required for any embedding, but in practice it turns out that the WebBrowser control doesn't do much with it. Most of the methods are able to just return S_OK or E_NOTIMPL. The most interesting one is IOleClientSite::GetContainer, which returns our IOleContainer implementation. IOleContainer is completely stubbed out and does nothing. But for some reason (apparently a bug in the underlying code), we have to support this for browser frames to work correctly. So we do return it when asked, although it doesn't do anything.
IAdviseSink is used to get asynchronous notifications to the container from embeddings. In practice, the only one the WebBrowser control seems to use much is IAdviseSink::OnViewChange. We tried using this notification to redraw the control using the control's IViewObject implementation, but in practice this seems to cause a bit of unnecessary flicker. It seems best to just ignore it and let the control's window procedure do all the work.
The most interesting method here is IOleInPlaceSite::GetWindowContext, where we hand out our implementation of IOleInPlaceFrame. Additionally, in OnInPlaceDeactivate, we implement a minimal undo state by calling the control's IOleObject::DoVerb(DISCARD_UNDOSTATE), which may be unnecessary for the WebBrowser ActiveX control, but is technically proper OLE protocol. Also, we are caching the QueryInterface for IOleInPlaceObject in OnInPlaceActivate, and releasing it in OnInPlaceDeactivate. This is needed because our container uses IOleInPlaceObject::SetObjectRects when it needs to resize the WebBrowser control.
IOleInPlaceFrame is a lot more interesting. This is the interface that deals with menu negotiation, UI adornment space negotiation, and so on. Figure 20 explains each of the methods we implemented.
Figure 20 IOleInPlace Frame Methods
IOleInPlaceFrame::GetWindow The application uses a child window to house the WebBrowser ActiveX control, CMSJOCCtr::m_hWndFrame. We just return this frame HWND to satisfy this method.
IOleInPlaceFrame::SetActiveObject This method gets called again and again as the user browses around and clicks on various objects. We cached the IOleInPlaceActiveObject passed in here, releasing the old one each time. The main point here is that the main app message pump uses this cached pointer, calling IOleInPlaceActiveObject::TranslateAccelerator first before processing any messages. This gives the current in-place-active object first crack at any keyboard messages, per the OLE compound document specification.
IOleInPlaceFrame::SetStatusText This is an important method. The WebBrowser control will call back to us with status text messages to display to the user. The sample code does nothing with the status text, but the hook is there to do something useful with it.
IOleInPlaceFrame::EnableModeless This is something the control can call with FALSE to let you know it's going to put up a modal dialog box or message box or otherwise go into a PeekMessage loop. The control must call it again with TRUE when done. The sample implementation sets a flag when called. If this flag is set to FALSE, the sample browser will refuse automation calls and refuse to navigate to another site because it means the control is in a state that should not be interrupted.
IOleInPlaceFrame has six methods that have to do with space and menu negotiation. In fact, the current implementation of the control does not do any menu or space negotiation. It passes a NULL HMENU and a BORDERWIDTHS structure containing all zeros to let us know this.
The only command we're implementing in IOleCommandTarget is OLECMDID_SETPROGRESSTEXT. This allows us to get another set of status messages for the user, besides the ones that come through in IOleInPlaceFrame::SetStatusText.
IServiceProvider is a new interface and is not necessary to host the WebBrowser control. It can be used to set up a custom handshake between an ActiveX control in the HTML page and your custom browser. Simply have your implementation of IServiceProvider::QueryService check for a custom SID and IID that only you and your control know about and you can return a COM interface to the control. Note, however, that calls the control makes on this interface will almost certainly not be on the same thread as you, and you'll have to make this work correctly. Refer to the COM spec and the explanation of the apartment threading model to understand the implications.
Don't you need to implement IOleControlSite to write an ActiveX control container? Well, we thought so at first, but in practice this container is so specialized that it only houses one particular ActiveX control (the WebBrowser control). IOleControlSite is not useful in this scenario, so we have deleted it from the sample in the spirit of keeping things as simple as possible. If you extend the sample to host any other controls, you'll probably want to implement it as one instance for each control, just as you'll need to implement multiple instances of IOleInPlaceSite and IOleClientSite.
We needed to trap the events fired by the WebBrowser control. This turned out to be pretty easy. QueryInterface the control for IConnectionPointContainer, then call IConnectionPointerContainer::FindConnectionPoint(DIID_DWebBrowserEvents) to get the correct instance of IConnectionPoint. Call IConnectionPoint::Advise with your implementation of IDispatch set up to handle the eventids described in exdispid.h from the Microsoft Internet SDK. Events.cpp implements many of the events and shows how to pull the arguments out of the DISPPARAMS structure that is passed when an event is fired. We return S_OK on all events.
DISPID_NEWWINDOW is a special case that we didn't handle in the sample code. This is effectively Internet Explorer telling us that it is being asked to perform a window.open from JavaScript or VBScript code in the page. It is giving us an opportunity to intercede and handle the request, effectively putting up a new popup window with its own instance of the WebBrowser control. If you don't handle this, a new instance of Internet Explorer will be spawned as a sort of last-ditch, drop-dead behavior. However, be aware that Web page scripts that try to do a window.open and then access objects in the namespace of the new window will fail, giving a VBScript or JavaScript error. The only way for you to make this work is to handle the window.open yourself, keeping the new instance of the WebBrowser control in the same process as the first instance. Have fun!
In this section, we'll discuss how to create a Web-based user interface. We center our discussion around work done to implement OnStage.msn.com. Our primary goal for the interface is to provide a list of services grouped into categories. Users should be able to choose a category, view a list of related services, and then pick a specific service that
they are interested in using. The interface should also have a method of providing a steady stream of promotional information.
Online services today are production studios and demand high-quality representation. The entire interface should be graphically rich. To reach a wide range of users, you'll need to entertain and intrigue people who range from the bookworm intellectual to the couch potato. Most Web sites that are rich in multimedia tend to be slow. Your interface must be fast and responsive enough for quick browsing.
Achieving all of this is the challenge. Today's computer users are jaded; they have seen the special effects at the movies and on TV, and they want to be impressed by their computer applications. To achieve the Wow! factor, you'll have to use the newest and best technology possible.
Some of the technology that you need is probably already implemented in Internet Explorer or in the many ActiveX controls available from third parties. Of course, if you are not satisfied with what is available or if you need something more specific, you can always implement your own ActiveX control.
There are several choices that we can make to implement such an interface. For our discussion, we will narrow it down to two criteria: how rich the experience is and how often you want to update or change the interface. By varying their importance, you can develop a range of solutions, from a monolithic hard-coded application to a totally Web-based one.
A monolithic application, probably built using C or C++, can give your users a very rich experience by creating a complete custom interface into the online service. Because of the application's direct ties into the operating system, its design is highly flexible and can achieve fantastic results for the user. However, it is more difficult to update and can lead to legacy issues. How do you change the look and behavior of the interface? How do you manage updates to the application? What happens when the interface and application become outdated? This puts yet another single-use program on the user's computer, taking up disk space. In all, this method is one of the more challenging directions to go with an Internet-based online interface.
The second alternative is to make your interface totally Web-based. By taking advantage of HTML standards, you can leverage the work done by the various vendors and standards bodies to create a simpler user experience. Text and image placement is described, and when the user clicks on something he or she can update the screen. A Web-based interface uses software the user already has and is quick and easy to update any time simply by changing the HTML.
However, today's HTML would hardly excite a consumer who regularly sees people morph into pools of liquid metal on the movie screen. So how do we create a compelling interface? We suggest using a combination of HTML and ActiveX controls. ActiveX controls give the power and flexibility we need to achieve the Wow! factor. Using the HTML and HTTP standards of the Internet will allow us to update at will by simply changing the source code. We will even use some proposed standards to push the technological edge. However, before we go over the top, we want to discuss some of the performance issues.
Performance is always in a developer's mind. We want our code to run as fast as possible on the slowest machine. Of course, most performance situations put these two concepts in opposition to each other. So in our example of an online interface, we should monitor four things to achieve a good performance balance: CPU use, download time, response time, and disk use.
Potential users on the Internet have huge variations in capabilities. The user could have an Intel 386 computer with a 2400 baud modem or the latest Pentium Pro with an ISDN line. For our discussion, we'll limit our users to those running Windows 95 on an Intel 486 or greater computer with a 14.4 Kbps modem. There are three components in tuning CPU usage for our interface: the browser, the ActiveX components, and the scripts that control the behavior.
Basing our interface on Microsoft Internet Explorer is a given. Microsoft has made great efforts to tune the performance and allow it to use the capabilities of Windows 95. It can perform several operations at the same time including updating frames, running scripts, and downloading components of a Web page. We must manage this capability and not ask it to perform too many things at once.
Using an ActiveX control is not without cost. ActiveX controls are applications and, therefore, take CPU time to manage and run. Well-built ActiveX controls should not overtake the computer and should balance themselves well with the other tasks at hand. Judge similar controls against each other to choose the one that works best.
Another consideration that affects CPU usage is the scripting that controls the behavior of the interface. Scripting language support in Internet Explorer is powerful, but not without cost. Internet Explorer compiles the scripting language into an internal bytecode language and then runs an interpreter over that language. The length of the script will affect compile time, but the user's perceived time will be dominated by the time it takes to retrieve the page over the Internet. Also, explicitly declare all your variables and use few global variables. The interpreter handles local declared variables very quickly; global variables and implicit local variables (those automatically created when you don't explicitly declare them) are accessed much more slowly. In general, if you're going to use a property or a global variable more than once, copying it to a local variable and using the local variable will be much faster.
When using analog modems rather than ISDN-class connections, download time is a large factor. To fine-tune this, you have to understand what your time costs are and how you can control them. Time costs can be generally grouped into Web server processing time, network latency and bandwidth, and the size and number of the components for your interface.
For our interface, the Web server is only involved in downloading our HTML pages. There are no server-based processing, scripts, or gateway programs. If your solution does include these components, be sure you understand their costs.
On the Internet, bandwidth and latency are a problem. Most Internet users are connected with at least an analog modem running at 14.4Kbps. On average, you can download 1KB per second on that configuration. So, if you have a 30KB graphic, expect it to take 30 seconds—quite a long time. In addition, the at-home user is connected to an ISP that is connected to a regional provider that is connected to a national pipeline. At each point, time is added. Just asking the server if a file is up-to-date takes time. Expect roughly one second for every roundtrip request you make of the server. Limiting the number of requests you make is, therefore, important. Reduce the number of graphical components and individual Web pages that your interface needs from the server.
When it's time for the user to download those graphical components, make them small. HTML, scripting code, graphics, sounds—everything adds up. An unassuming page with a background sound, three graphics, and a page of text can quickly become a 45-second download. For that long a time, the page had better be impressive. If it isn't, then scale back your artistic production or look at other technologies. You don't want your users to fall asleep or leave your site because it takes too long to view your content.
Once enough of our interface is downloaded, we need it to be responsive to the user. When the user acts in some way on the interface, such as moving or clicking the mouse, the interface needs to react: colors need to change, items should move, or text should appear. Without this reaction or feedback, the user will become confused or frustrated with the interface. Design it to react well and keep the user engaged.
Finally, any code you download to the user will potentially reside on their hard drive. It's a good idea for your code to persist on the hard drive so it doesn't have to be downloaded each time. However, the user pays the price by no longer having the space available for other data.
Now, let's take a look at the ActiveX controls and scripting used to build the OnStage page of MSN (see Figure 21). Above the channel, a spotlight graphic shines on the list of shows. As the user moves the cursor over the list, the names turn blue. When the user clicks one, it turns red and starts playing promotional information in the middle area. The user can click on the promotional information to go directly to the service. Finally, at the right, a graphic for the channel is shown. If the user clicks another channel button, the names, promotional information, and art all change to represent the new channel.
Figure 21 On Stage!
After understanding our goals, thinking through performance issues, choosing an implementation method, and surveying the controls available to implement our interface, we chose to use a mix of Microsoft controls, a control from FutureWave Software (http://www.futurewave.com), and a custom control.
The HTML Layout control is used to provide the specific layout of items on a Web page. It is based on the CSS layout specification of the World Wide Web Consortium (W3C). It supports coordinate-specific placement, z-ordering for layering ActiveX controls, transparency, and ActiveX scripting. The HTML Layout control installs itself with several additional controls. We will be using its label control, hotspot control, and image control. The label control is used to display and format text. The hotspot control creates a specific region and fires useful events when the mouse enters, moves within, or exits a region. The image control displays many formats of images and downloads them asynchronously. Each control is implemented in the same DLL and allows runtime changes to almost any of its properties.
We also needed a control to provide rich graphics and scripting integration, but with a small download cost. Among the many ActiveX controls, FutureWave Software's FutureSplash Animator worked best for us. It creates great graphics based on vector-based drawings, then lets it stream down to the user's machine and allows the user to view and interact with the graphic before it is completely downloaded. FutureSplash integrated well into the ActiveX scripting model by exposing control of the animations and implementing events that can be fired from the animations on a user action or a change in the animation.
We used one custom control that reads from a standard INI file. The INI file contains the categories and lists of services for them. Figure 22 describes some other capabilities of the control. It also self-updates the INI file from a central location to keep up-to-date with any change in services. Having this control allows us to update and change the resource information (service names and URLs) with a small file download and have none of the resource data hardcoded into the scripting code—generally a good practice.
Figure 22 INI Control
Property/Method | Description |
SetCategory | Sets the current channel on which the control provides information |
ServiceCount | Number of services in the current channel |
GetName(i) | Text name for the specified service in the current channel |
GetURL(i) | URL for the specified service in the current channel |
The interface is built with two frames. The default.htm file creates the frameset. The frame named global loads global.htm and has a height of zero. This is a great way to hide functionality from the user and allow it to persist across pages. We used this technique to hold the INI control, but in other projects we played music and performed other background processing in the hidden frame. The frame named content has the full height of the screen and loads channel.htm.
Channel.htm is a simple file that contains the object declaration for the HTML Layout control. This file is an example of an active layout control (ALX). Notice that we have set the width to zero. This is to prevent a gray box from appearing while the control is initializing. All the controls and scripting code for the interface are hosted on the layout control. Once initialized, the layout control then loads the channel.alx file.
The channel.alx file contains all the object declarations for the ActiveX controls that are to be hosted. It specifies their initial placement, height, width, and any control-specific parameters. Unless you like tedious work, we recommend using the Microsoft ActiveX Control Pad editor (http://www.microsoft.com/workshop/author/cpad). It lets you graphically position the controls, set parameters, and even write scripting code.
The OnLoad event fires once the ALX has been loaded and starts the interface. We check to see if all the controls have successfully started and, if not, call a subroutine in the global frame that directs the user to a setup page. Next, we hide the controls the user shouldn't see yet and position the label and hotspot controls. Notice that we use a little-known collection in the HTML Layout control called Controls. This collection can be used to index and access any other control on the Layout control. This is fantastic for making your code easier to write and smaller to download. Here, we are using it to loop through all the label and hotspot controls. Using this method reduces 40 lines of code to seven. Last, we begin loading the channel buttons into the FutureSplash control we named categories. The FutureSplash control then fires OnProgress events as it is downloading the animation. When it is done, we play a splash screen animation for the online service in a second FutureSplash control named promo.
Now the user is free to choose any channel by clicking one of the channel buttons. When clicked, the FutureSplash control fires an event called FSCommand. It passes two arguments, of which we are only interested in the command argument; it contains the channel number that the user clicked. After stopping and resizing the FutureSplash control that played the splash screen, we set a global variable that tracks the current channel and call ChannelBuild to update the interface.
The four components of the upper area are the spotlight, the channel graphics, the promotional information, and the service names. ChannelBuild positions the first three of these on the screen, updates the channel graphic, and calls MenuBuild to update the service names. MenuBuild uses the INI control to get the text to display in the label controls. We position the text on the screen depending on the channel because the art director felt that changing the justification of the names was appealing. Finally, the interface can handle up to eight services per channel, so the routine hides the label and hotspot controls that are not needed.
The user now has a view of the services for the channel and art representing it. As the user moves the mouse over the names, the hotspot control fires MouseEnter and MouseExit events. In those event handlers, we change the color of the text to give the user feedback. If the user clicks on a name, the hotspot control fires a Click event. We then change the text to red and play promotional information in the FutureSplash control named promo.
The last piece of functionality to implement is getting the user to the service. This is done by making the FutureSplash animation for the promotional information fire an FSCommand event when clicked. The handler for this event looks up the URL for the service using the INI control and navigates the user there.
Here are a few things that we used when developing this interface. Place a message box call (msgbox) in troublesome code to alert you or show variables. You can also place several of them in different areas so you can trace the flow of the code. The message box stops the execution of the code until you acknowledge it, so timing-dependent code will not operate correctly.
Using the status bar on the browser is another method of providing debugging output. You can use it much like message boxes with the additional benefit that you don't have to acknowledge it. The drawback is that they are commonly short-lived, since Internet Explorer also uses the status bar to provide feedback when it is loading files from the Internet.
The best development in troubleshooting and debugging your Web-based applications is the new Microsoft Script Debugger. You can get it at http:www.microsoft.com/intdev/scriptieldbgdown.htm. It allows you to debug scripted HTML pages written in VBScript or JScript™. You can set breakpoints, step through code, view the call stack, query or change the value of a variable while code is running, and call procedures. It enhances Internet Explorer by adding a new debug object so you can output debugging text to a special immediate window. It also introduces a new statement that lets you suspend code execution and open the debugger when it is encountered in code.
In this article, we presented the infrastructure of the Web-based MSN online service. We provided usage and technical details for IIS, Commercial Internet Chat Server and News Server, and Active Server Pages. We showed you how to design ActiveX containers to host Internet Explorer 3.x as an ActiveX control. Finally, we discussed how we used client-side scripting to make our sites sizzle. We wish you the best of luck writing your Web-based app and hope we were able to give you some ideas here.
This article is reproduced from Microsoft Systems Journal. Copyright © 1997 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.
To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S. and Canada, or (303) 678-0439 in all other countries. For other inquiries, call (415) 905-2200.