Cutting Edge: Binary Behaviors in Internet Expl...

We were unable to locate this content in de-de.

Here is the same content in en-us.

Binary Behaviors in Internet Explorer 5.5
Dino Esposito
Download the code for this article: Cutting0101.exe (181KB)
W
hen I began my coverage of Microsoft® Internet Explorer 5.5 behaviors in the December 2000 issue, I promised a look at binary behaviors this month. As I noted last month, behaviors evolved to work around the limitations of scriptlets. Like any technology, scriptlets had their pros and cons. They provided a complete separation of the document object models (DOM) from the hosting page and the scriptlet itself, while allowing a page and component to communicate with each other by exchanging references to their respective DOMs. The downside was that since the separation was so clean, there was no way to integrate host and scriptlet. In other words, the scriptlet was not linked or embedded in the hosting page. Although it was capable of communicating, it remained separate. This produced a number of nasty side effects. For example, it became impossible to print the output of a scriptlet as part of the host page.
      Behaviors were introduced with Internet Explorer 5.0. See "Scripting Evolves to a More Powerful Technologyâ€"HTML Behaviors in Depth," in the April 1999 issue of Microsoft Internet Developer for a primer. While they were designed to overcome limitations inherent in scriptlets, behaviors ended up magnifying other side effects. Behaviors fully integrate with the page, and their dynamically generated DOM is intertwined with the primary DOM and accessible through DHTML. This poses practical as well as theoretical problems. For example, a behavior can inadvertently override a page-wide Cascading Style Sheets (CSS) setting. What if your behavior ends up inheriting undesired page-wide styles? What if it duplicates one of the existing element IDs? This particular scenario can generate insidious errors. Suppose you have script code like this:
myElem.href = "http://expoware"
As long as myElem is a single IDâ€"that is, there's just one occurrence of it in the whole DOMâ€"this code will work just fine. When a behaviorâ€"or something elseâ€"inserts a new element with the same ID, myElem is assumed to be a collection, and the previous code won't work anymore. As a result, your code will abruptly misbehave. What's worse, the cause won't be obvious at all. Element and view-linked behaviors have addressed these problems, as I explained last month, but there is more work to be done. Enter binary behaviors.

What's a Behavior, Anyway?

      Often, while teaching my scripting classes, just when I'm eloquently explaining the most advanced features, someone stands up and says, "OK Dino, but in practice, what is a behavior?" It's not such a tough question, but isn't simple either.
      A behavior is a component that implements two interfaces, but in both cases it relies heavily on COM component technology. You can write behaviors in three ways: as HTML components (HTC), Windows Script Components (WSC), or COM objects. When you use raw COM interfaces, or the WSC infrastructure, the resulting behavior looks unequivocally like a COM object. When you write it in the HTC formatâ€"an XML vocabularyâ€"the appearance is somewhat different and may be confusing. The interaction between Internet Explorer and behaviors always takes place through COM. But behaviors can have a number of software layers that hide the underlying COM interfaces that actually do the job. So, to answer the question, the definition of a behavior is a COM object that exposes a number of well-known interfaces. However, you can write behaviors without worrying about the nitty-gritty COM details by using script or even declarative XML (see Figure 1).

Figure 1 Writing Behaviors
Figure 1 Writing Behaviors

      Writing a behavior without any WSC or HTC wrapper is significantly more difficult and requires specific COM or ATL skills in addition to familiarity with DHTML. There are a number of advantages to using raw COM calls and, consequently, binary behaviors. First, your code is not accessible and cannot be snooped, unlike a client-side script-based solution, which can expose the code to any Peeping Tom. A second, and potentially more significant, limitation is that a script-based behavior is considered part of the page and, as such, is subject to the standard security restrictions of HTML pages. This means that you cannot access or download any resource outside the current domain. For example, using the WSC or the HTC format you cannot even think of providing the same functionality as the standard download behavior of Internet Explorer 5.0. Binary behaviors have a third advantage. Because they are compiled, not interpreted, they generally run faster than scripted components.
      However, if you're considering binary behaviors only because you want to protect your source code, you might be better off trying script encoding first. The Script Encoder (see http://msdn.microsoft.com/scripting for usage and download information) is a tool that allows you to encrypt the script code blocks in any HTML, ASP, or WSC document.

COM Interfaces of a Behavior

      A binary behavior is a COM object that exposes at least two interfaces: IElementBehavior and IElementBehaviorFactory. The former provides the actual behavior for the component. IElementBehaviorFactory instantiates the object in the context of the browser. Optionally, a behavior could implement additional interfaces, such as IElementBehaviorCategory and IElementBehaviorRender. IElementBehaviorCategory provides a way of identifying and cataloging behaviors by category in much the same way as Implemented Categories work for COM objects. IElementBehaviorRender enables a behavior to participate in the rendering of text and objects on a Web page. (More on this later.)
      IElementBehavior and its factory interface IElementBehaviorFactory are the two key interfaces, and they're mandatory for creating behaviors. Figure 2 shows their methods. If you know how to design and create behaviors with HTC or WSC, writing binary behaviors should not be much more difficult, except that you'll be using C++ or ATL.

A Simple Binary Behavior

      Anyone who has ever attempted to explain what a behavior is and how it works eventually uses the mouseover sample. Behaviors are the perfect way to make each HTML tag sensitive to a user's activityâ€"by having them change color when the mouse enters and exits the client area, for example. But you don't need a behavior to get this functionality. You just need a couple of event handlers: one for onmouseover and one for onmouseout. Here is the simplest way to do that:
<a href="http://expoware"
    onmouseover="style.color='red'"
    onmouseout="style.color=''">
Visit my Web site
</a>
With behaviors, you can hide the script code for the event handlers for a more elegant and declarative syntax.
<a href="http://expoware"
    style="behavior:url(mouseover.htc)">
Visit my Web site
</a>
The previous snippet applies the behavior of the specified HTC file to the anchor (<a>) tag. The actual code for mouseover.htc is shown in Figure 3. As you can see, it is an extremely compact and readable piece of code. You can obtain the same behavior from C++ or ATL code, but it will be far more complex. Figure 4 contains the C++ headers for the objects that form a mouseover behavior.
      As mentioned before, in its simplest form a binary behavior is just a COM object that implements two interfaces. To make things really interesting, though, you usually need to implement a connection point object to sink some of the DHTML page events (such as onmouseover and onmouseout). The C++ header for this sink object is featured in Figure 5. So you need to have three ATL objects in a minimal, yet functional, binary behavior: the behavior, the behavior factory, and an object that implements one of the DHTML outgoing interfaces. Of course, this third object is the counterpart of the <PUBLIC:ATTACH> tag in HTC files.
      Figure 6 shows a typical implementation for IElementBehavior and IElementBehaviorFactory. In the body of Notify you can hook on the behavior's initialization events, such as oncontentready and ondocumentready. Oncontentready indicates that the subtree rooted on the element to which the behavior is attached has been fully parsed. Ondocumentready lets you know that the whole document object model has been successfully created. Figure 7 illustrates the mapping between typical HTC tags and the COM counterpart of binary behaviors.

The Programming Interface of Behaviors

      A behavior is required to expose an interface to allow explicit creation. This is just what the IElementBehaviorFactory interface does. Whenever the client code directly invokes addBehavior, or the browser encounters a tag with an associated behavior, the COM object that represents the behavior is loaded. The caller queries for IElementBehaviorFactory and then calls FindBehavior. At this point, the component is ready for further initialization. Init indicates that this process has just started. At this time, a typical behavior would cache the pointer to its entry point into the hosting DOM. This is represented by an IElementBehaviorSite interface pointer. Through this interface, the behavior will know about its attached element.
m_spSite = pBehaviorSite;
CComPtr<IHTMLElement> m_spElem;
m_spSite->GetElement(&m_spElem);
      The m_spElem data member contains the C++ counterpart of the DHTML element object. The association between behaviors and the related tags (I'm not talking about element behaviors here) can be broken programmatically by calling removeBehavior from the DHTML object model. This evaluates to calling the behavior's Detach method. This is also responsible for freeing any allocated memory when the behavior gets automatically detached upon tag destruction.
      Basically, a behavior does four things. It can expose methods, properties, events, and hook for DHTML events at any allowed level, including document, window, and element.
      You expose methods and properties by implementing IDispatch on your behavior's coclass and overriding both GetIDsOfNames and Invoke. In the first case, you just return the dispIDs of the methods or properties you support. Within the body of Invoke, though, you check the flag attribute, which indicates whether the call is trying to get or put the value of a property or calling a method. Then, you properly set the array with the parameters for the Invoke method to process. In particular, you should specify an input argument for a DISPATCH_PROPERTYPUT flag and an output argument for a DISPATCH_PROPERTYGET.
      The official demo of binary behaviors, AtlBehave, which can be found on both MSDN® Online and the latest Platform SDK, provides a functional example of this feature. The demo shows you how to expose your own events. You declare events quite easily with script and XML code:
  <PUBLIC:EVENT NAME="onResultChange" ID="rcID" />
Then you fire them from your code by first creating a so-called event object
  oEvent = createEventObject();
and then raising it through its ID or by name:
  rcID.fire(oEvent);
  fireEvent("onResultChange", oEvent);
      By design, events are exposed by a certain component, but look like callback entry points for clients to inject their own code. The client of a behavior can only be the DOM that hosts it. Thus, there must be an interaction between the behavior and its host. This interaction takes place the COM way through site interfaces.
      I already mentioned the IElementBehaviorSite interface that provides the behavior with the key reference to the main site. There's an additional and dedicated site interface to cope with events, which is called IElementBehaviorSiteOM.
      First, you get a reference to IElementBehaviorSiteOM and cache it for further use.
m_spSite = pBehaviorSite;
m_spSite->QueryInterface(
__uuidof(IElementBehaviorSiteOM), 
(void**)&m_spSiteOM
);
You implement this code from the Init method; note that pBehaviorSite is a pointer to IElementBehaviorSite. The m_spSiteOM pointer will be used later in the code to fire events according to the internal activity of the behavior. In Figure 8 you can see the typical piece of code that fires a custom event called oninitcomplete. Notice a clear similarity with what you do from script. The script methods are just wrappers for the methods of the IElementBehaviorSiteOM interface provided by Internet Explorer 5.0 and higher. Keep in mind that a custom event always must be registered through a call to the RegisterEvent method of the IElementBehaviorSiteOM interface.
m_spSiteOM->RegisterEvent(L"oninitcomplete", 0, NULL);
This is equivalent to using the <PUBLIC:EVENT> tag.

Sinking DHTML Events

      Unless you're writing element behaviors with an associated set of custom HTML tags (I'll cover this later on) there's a good chance that you need to detect DHTML events. This can be accomplished through a sink object that connects to the outgoing interface of the specified target element and handles events via IDispatch. Let's see how.
      I'm going to show the COM equivalent for all <PUBLIC:ATTACH> tags you may have in your HTC, such as:
<PUBLIC:ATTACH 
event="onmouseover" 
handler="fnOver" />
      There are two things to do at this point. First, add a new object to your ATL project and make sure it inherits from IDispatchImpl. Make this class a friend of your behavior's coclassâ€"that is, add a forward reference to the class you wrote that implements IElementBehavior. You need to override the IDispatch's Invoke to make sure you properly handle the dispIDs of the events you're interested in. And, more importantly, you need to expose the DHTML outgoing interface you want to sink.
BEGIN_COM_MAP(CDHTMLEvents)
    COM_INTERFACE_ENTRY2(HTMLElementEvents, IDHTMLEvents)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
Figure 9 shows the header file for this kind of object. HTMLElementEvents is the name of the interface with all the connectable events for HTML tags. (You can check MSDN Online for the full list of events.)
      The second thing you need to do is tell ATL that you want to receive notifications for all the events listed in the specified interface. During the behavior's initialization, run the following code:
m_spSite->GetElement(&m_spElem);
CComObject<CDHTMLEvents>::CreateInstance(&m_pEventSink);
m_pEventSink->m_pBehavior = this;
AtlAdvise(m_spElem, m_pEventSink, 
HTMLElementEvents, &m_dwCookie);
      After creating an instance of the sink object, make sure you cache the pointer to the behavior's coclass. This will turn out to be very useful later on for accessing information about the current element and its style and, more generally, about the surrounding DOM. Through AtlAdvise, you're using the IDispatch implementation of m_pEventSink to handle all or some of the events defined by HTMLElementEvents for the specified m_spElem element. After this code has been processed, your behavior starts receiving calls for the various events, as shown in Figure 10. There's nothing more that behaviors need to do in order to be usable through CSS styles from HTML pages.

Using Binary Behaviors

      A binary behavior is a COM object and, as such, subject to all the standard security restrictions of COM objects. Depending on the security settings for the specific machine, you may receive warning messages as soon as Internet Explorer realizes the presence of that object. To avoid this, you can mark the behavior safe for scripting and initialization. With ATL, this only requires that you inherit the Factory object from IObjectSafetyImpl, as shown in Figure 4.
      You place a behavior on an HTML page using the <OBJECT> tag.
<OBJECT ID="MyBeh"
CLASSID="clsid:37449D2A-2F25-4296-9ABF-BF7A7CD0A6AF">
</OBJECT>
      If you have a CAB file for the behavior and want it to automatically download, you can add the CODEBASE attribute and make it point to the CAB file. Be sure to assign the behavior an ID, otherwise it will be impossible for you to associate it with any tag.
<span style="behavior:url(#mybeh)">
Hello, world
</span>
Figure 11 Change Color
Figure 11 Change Color

      The syntax of the CSS behavior style is slightly different for binary behaviors. Instead of using the file name (as for HTC files) or the CLSID, you just utilize the ID prefixed by a # symbol. That's part of the reason you must assign it an ID. In Figure 11 you can see the text inside a <SPAN> tag that turns red whenever the mouse passes over it. In the same HTML page you'll see a <BUTTON> tag and an anchor.

Figure 12 No Change
Figure 12 No Change

Figure 12 shows what happens if you assign the same behavior to them. Nothing happens! The behavior only works for text elements like <SPAN>â€"not with anchors or buttons. The HTC counterpart described earlier works just fine, however.

Figure 13 COM Object Viewer
Figure 13 COM Object Viewer

      Figure 13 shows all the interfaces registered on my machine. As you can see, almost every HTML tag has its own outgoing interface for events. In the current implementation of the behavior I only considered HTMLElementEvents. To solve the problem, you need to connect to the right interface based on the tag name. Here's an example.
IID intf;
m_spSite->GetElement(&m_spElem);
CComBSTR b;
m_spElem->get_tagName(&b);
if (b == L"A")
    intf = DIID_HTMLAnchorEvents;
else if (b == L"BUTTON")
    intf = DIID_HTMLButtonElementEvents;
else
    intf = DIID_HTMLElementEvents;
      You read the name of the tag that the behavior is associated with and then enter a switch statement to set an IID variable with the IID of the proper outgoing interface. Next, you call AtlAdvise in a more general way:
AtlAdvise(m_spElem, m_pEventSink, intf, &m_dwCookie);
      An ATL binary behavior is normally given by a number of different objects, each with its own CLSID. Which one should you use to insert the behavior in the page using the <OBJECT> tag? You should use the CLSID of the Factory object, of course. Make sure you never use the word "default" as a behavior's ID, otherwise it will conflict with the ID of predefined system behaviors. Of course, you can apply multiple behaviors (binary and script-based) to an element by specifying a space-delimited list of URLs. For example,
<IMG style="behavior:url(beh1.htc) url(beh2.htc)" >
      A behavior can belong to a category provided it implements IElementBehaviorCategory. It requires just one method called GetCategory that retrieves the categories to which the DHTML behavior belongs. I wouldn't base applications on this feature, though, as it is subject to change with future versions of Internet Explorer.

Self-rendering Behaviors

      So far I've considered behaviors that simply subclass and affect the way in which existing tags work. Binary behaviors, though, have another pretty cool feature. They can draw directly on the client area of Internet Explorer. The final effect is somewhat similar to what ActiveX® controls do. By implementing an extra interface, you'll be given access to the Internet Explorer device context handle and you'll be able to draw just like you can in a normal window using the full set of Win32® API functions. There's a technical difference from the way ActiveX controls draw on the browser window. You're drawing directly in the Internet Explorer window, not on a separate window.
      The internal details of self-rendering behaviors changed quite a bit between Internet Explorer 5.0 and 5.5. The involved interfaces have been revamped for Internet Explorer 5.5, and the oldest are now deprecated and shouldn't be used anymore. In terms of code portability, nothing (or very little) is lost because the new interface, called IHTMLPainter, has nearly identical methods and follows similar logic.
      With Internet Explorer 5.0 you were supposed to implement IElementBehaviorRender to provide your own user interface and rely on IElementBehaviorRenderSite to get drawing information from the browser. IHTMLPainter provides a programming interface to MSHTMLâ€"the Internet Explorer rendering engineâ€"so that it can ask a rendering behavior to paint its user interface. The host-side equivalent interface is IHTMLPainterSite.

Figure 14 Inheriting from IHTMLPainter
Figure 14 Inheriting from IHTMLPainter

      There's no other difference between self-rendering and standard behaviors. When the browser finds a behavior on its way, it automatically gets a pointer to IElementBehavior and queries for IHTMLPainter to determine whether the behavior also has rendering capabilities. In Figure 14 you can see one of the MSDN samples opened in Visual Studio® 6.0. The workspace content is rather similar to the test behavior I built earlier. The only difference is the IHTMLPainter base class added to the inheritance list. Figure 15 shows the methods you must implement for a fully functional rendering behavior. The Draw method is by far the most important one for rendering. As you can see in the following code, the Draw method receives the bounding rectangle for the element to which the behavior is attached and the rectangle section that needs to be refreshed.
HRESULT Draw(
    RECT rcBounds,
    RECT rcUpdate,
    LONG lDrawFlags,
    HDC hdc,
    LPVOID pvDrawObject
);
      Of HDC and pvDrawObject, only one will be set at a time because they are mutually exclusive parameters meant to indicate the drawing context in use. It can be either the GDI or DirectDraw®. Figure 16 shows a sample rendering behavior in action.

Figure 16 A Rendering Behavior
Figure 16 A Rendering Behavior

Binary Element Behaviors

      Element behaviors are a new feature of Internet Explorer 5.5. They extend existing behaviors (renamed attached behaviors) in terms of the browser support for custom HTML tags. If you want behaviors to give birth to custom HTML tags, then use element behaviors. See Cutting Edge in the December 2000 issue of MSDN Magazine for more information.
      The two remaining interfaces required to build a binary behavior are IElementNamespaceFactory and IElementNamespaceFactoryCallback. IElementNamespaceFactory provides a mechanism for creating element behaviors for a certain namespace. You know about the involved namespace through an IElementNamespace pointer that you get from the only method to support. Here's a typical implementation of an IElementNamespaceFactory coclass:
HRESULT CFactory::Create(IElementNamespace *pNamespace)
{
CComBSTR bstr = L"MyTag";
pNamespace->AddTag(bstr, 0);
return S_OK;
}
This method call is the equivalent of the following declaration in an HTC file:
<PUBLIC:COMPONENT tagName="MyTag">
The previous code establishes an irrevocable link between the behavior and the element name. It's worth repeating that an element behavior can be associated only with a particular tag name, and only one at a time. Also, an element behavior instance follows the same lifecycle as the attached element.
      The IElementNamespaceFactoryCallback interface is invoked when Internet Explorer 5.x encounters an unknown tag that is part of a namespace, for example a custom tag. Notice that Internet Explorer 5.x always ignores unknown tags without an associated namespace. The browser wraps all the available information about the tag in the call to the only method of the IElementNamespaceFactoryCallback interfaceâ€"Resolve. Unless you need to sort out potential name conflicts or solve any other naming issue, use the typical implementation for the interface, which looks like this:
STDMETHODIMP CBehavior::Resolve(
    BSTR bstrNamespace, 
    BSTR bstrTagName, 
    BSTR bstrAttrs, 
    IElementNamespace* pNamespace)
{
    return S_OK;
}
      An element behavior must be irrevocably associated with a tag name because an element behavior is supposed to be the software module that gives life to an otherwise unknown tag. For this reason, you need to make sure that in the behavior's source code the tag it has been attached to is exactly the tag it's been designed to power. Normally, this is a test you execute in IElementBehaviorFactory::FindBehavior:
CComBSTR bstr = L"MyTag";
if (wcscmp(bstrBehavior, bstr) == 0) 
{...}
Of course, bstrBehavior is one of the input arguments of the method.

Binary-only Features

      Binary element behaviors can provide some extra features that script-based element behaviors cannot. In particular, they can draw their own focus rectangle, participate in the overall layout process, and submit data when used from within an HTML form. These additional capabilities are offered by the following three optional interfaces: IElementBehaviorFocus, IElementBehaviorLayout, and IElementBehaviorSubmit.
      All of them have an extremely simplified set of functionsâ€"often just one. This is certainly the case with IElementBehaviorFocus, which has only the GetFocusRect method. It is supposed to return the bounding rectangle for the behavior's customized focus. To take part in the page layout process and determine its actual position and size, a behavior needs to implement IElementBehaviorLayout. The GetSize method is then synchronously invoked to pass the proposed size to the behavior for approval and needed changes. Pay close attention to the code you put into this function if you want the browser to continue working properly and not hang. Otherwise, the MapSize method gets called to resize a behavior's site.
      IElementBehaviorSubmit represents the key code to enable a behavior resident on a form to submit its data. Normally, in fact, neither ActiveX controls nor binary behaviors can just participate in the form submission process. A good all-purpose workaround is to intercept the onsubmit event or the onclick event of the submit button, and fill a hidden INPUT field with the information you want your components to export. (See Web Q & A in the September 2000 issue of MSDN Magazine.) However, Internet Explorer 5.5 slightly changed the way in which form submit requests are processed. Upon submitting a form, Internet Explorer 5.5 now queries each element behavior found in that form for IElementBehaviorSubmit. It then uses the GetSubmitInfo method to allow the behavior to pass the needed data to submit. It calls Reset to inform the behavior that the user clicked on the reset button and gives it a chance to deallocate or clean any buffer.

Binary and Script Trade-offs

      Overall, writing binary behaviors is not as easy as writing script behaviors because of the relatively complex nature of COM and C++ and the large number of features you can implement. But this is the price you pay for the enhanced performance of compiled binary behaviors. It's worth trying to write a script behavior to accomplish your objective before writing a binary behavior.
      In addition to performance, another good reason to use binary behaviors is access to HTTP and the full Win32 API. In particular, binary behaviors aren't subject to security domain restrictions and can connect to any URL. On the other hand, they're subject to ActiveX security rules.
      If you're still not convinced of the benefits of binary behaviors, consider writing them only if you want an advanced user interface along the lines of the one shown in Figure 16. Most of the time, script-based behaviors will meet your requirements.
Dino Esposito is a trainer and consultant based in Rome. Author of several books for Wrox Press, he now spends most of his time teaching classes on ASP.NET and ADO.NET. Get in touch with Dino at desposito@vb2themax.com.

From the January 2001 issue of MSDN Magazine

Page view tracker