Windows 7 Accelerator Platform

The Accelerator Platform is a powerful tool for integrating Web services in Windows Internet Explorer 8, but it's actually surprisingly simple to use from within other applications on Windows 7. This article describes how a developer might access the features of the Accelerator Platform from code with examples in C#.

This article contains the following sections:

  • Introduction
  • Process Overview
  • Enumeration
  • Input
  • Output
  • Execution
  • Preview
  • Managing Accelerators

Introduction

Let's take a scenario: a user comes across an obscure word while browsing a document and would like to define it by using an online dictionary. What would that user have to do? In general, copy the selection, leave the document application, open a new browser window, navigate to the dictionary service, paste in the selection, receive the output, and then return to the original application.

This is part of a broad paradigm of user actions that follow the general pattern of copy-navigate-paste, and it's symptomatic of a fundamental disconnect between users and the Web services they interact with most. Windows Internet Explorer alleviates this problem with Accelerators, which, in the previous example, enable users to highlight text, bring up a context menu, and click to send the selection to a dictionary service of their choice.

Taking that one step further, you can imagine that other programs might face the same problem, and that Accelerators could be a great way for them to interact with Web services, too. That's where the Accelerator Platform on Windows 7 comes in.

For example, imagine being able to select text in Microsoft Word, right-click, and then find Accelerators in the context menu, as shown in the following illustration.

Figure 1: Accessing the Accelerator Platform from Word 2007

As it turns out, such functionality is quite possible with the Accelerator Platform, and this article will discuss how to build on that functionality by using the add-in above to provide context.

The rest of this paper is devoted to interacting with the Accelerator Platform, primarily its three main functions: enumeration, preview, and execution of Accelerators. It also talks briefly about of the management functionality the platform provides.

Process Overview

In broad strokes, here are the steps a developer would generally take to interact with the Accelerator Platform. (Incidentally, this is nearly the same algorithm that Internet Explorer itself follows in enumerating, executing, and previewing Accelerators.)

  1. Acquire an IOpenServiceActivityManager interface.
  2. Use it to acquire a category enumerator.
  3. For each category, acquire an Accelerator enumerator.
  4. For each Accelerator, handle events corresponding to execution and preview in the host application.

Note that a developer will need to implement two interfaces: IOpenServiceActivityInput and IOpenServiceActivityOutputContext. These interfaces provide methods for feeding input to Accelerators and displaying output to users, respectively. Implementing these interfaces will be covered in the Input and Output sections.

Enumeration

The first thing a developer will probably want to do is enumerate the Accelerators on the system, so users will have a choice of which services they can interact with.

First things first: we need to get an IOpenServiceActivityManager interface:

IOpenServiceActivityManager osam = (IOpenServiceActivityManager)new OpenServiceActivityManager(); 

By using the manager, we can now call IOpenServiceActivityManager::GetCategoryEnumerator:

IEnumOpenServiceActivityCategory cats = null;

osam.GetCategoryEnumerator(OpenServiceActivityContentType.ActivityContentSelection, out cats); 

The first parameter tells the platform that we want to enumerate Accelerators that can operate on the selection context. Similar types for link and document context exist as well. The IOpenServiceActivityInput::GetType method is particularly useful here. It's covered in more detail later.

Now we can use the IEnumOpenServiceActivity::Next function of the category enumerator to loop through all the categories:

uint numreturned = 0;
IntPtr[] ptr = new IntPtr[1];

while (true)
{
    cats.Next(1, ptr, out numreturned);
    if (numreturned != 0)
    {
        object catobj = Marshal.GetObjectForIUnknown(ptr[0]);
        IOpenServiceActivityCategory cat = (IOpenServiceActivityCategory)catobj;
    }
    else
    {
        break;
    }
} 

Note that the IEnumOpenServiceActivity::Next function has an out parameter. This parameter holds the number of categories returned by a given call to the function. So one could, potentially, create a larger array of type intptr and reduce the number of loops performed by the code.

For each category, we can now get an iterator for Accelerators:

IEnumOpenServiceActivity activityenum = null;

cat.GetActivityEnumerator(new MyActivityInput(), new MyActivityOutput(), out activityenum); 

MyActivityInput and MyActivityOutput are implementations of IOpenServiceActivityInput and IOpenServiceActivityOutputContext, respectively. These classes give the platform more information about which Accelerators to enumerate, and they'll be covered shortly. Note that you can just pass in null to enumerate all Accelerators.

Using the iterator, we can now enumerate all the Accelerators for the category:

while (true)
{
    activityenum.Next(1, ActivityPtr, out ActivitiesReturned);
    if (ActivitiesReturned != 0)
    {
        object oActivity = Marshal.GetObjectForIUnknown(ActivityPtr[0]);
        IOpenServiceActivity Activity = (IOpenServiceActivity)oActivity;
    }
    else
    {
        break;
    }
} 

Input

Before we can execute or preview an Accelerator, we need to provide input in a way it can understand. In other words, we need to implement the IOpenServiceActivityInput interface. Here's a skeleton definition:

public class MyActivityInput : IOpenServiceActivityInput
{
    public void GetVariable(string pwzVariableName, string pwzVariableType, out string pbstrVariableContent)
    {
        throw new NotImplementedException();
    }
 
    public void HasVariable(string pwzVariableName, string pwzVariableType, out bool pfHasVariable)
    {
        throw new NotImplementedException();
    }
 
    public void GetType(out OpenServiceActivityContentType pType)
    {
        throw new NotImplementedException();
    }
} 

The platform calls each of these methods to get information it can use in working with an Accelerator. Of course, we'll want more than a skeleton, so let's walk through each of the methods in detail.

The IOpenServiceActivityInput::HasVariable method is pretty straightforward. The platform passes in two strings representing the variable and its type, and IOpenServiceActivityInput::HasVariable decides whether to tell the platform if it has that variable or not through the out parameter. In the following example, we're essentially telling the platform that we'll only return a value for the {selection} and {documentUrl} variables.

public void HasVariable(string pwzVariableName, string pwzVariableType, out bool pfHasVariable)
{
    if (pwzVariableName == "selection" || pwzVariableName == "documentUrl")
    {
        pfHasVariable = true;
    }
    else
    {
        pfHasVariable = false;
    }
} 

Note  Even though {documentUrl} may not seem to serve a purpose outside the context of a Web browser, the platform uses it to verify the security zone and scheme of the input when making calls to IOpenServiceActivity::CanExecute, IOpenServiceActivity::Execute, IOpenServiceActivity::CanPreview, and IOpenServiceActivity::Preview. The variable is checked against the homepageUrl value specified by the Accelerator XML, so it's important to return a well-formed URL.

 

If the out parameter from IOpenServiceActivityInput::HasVariable is true, then the platform can call IOpenServiceActivityInput::GetVariable to actually retrieve the variable, as in the following example:

public void GetVariable(string pwzVariableName, string pwzVariableType, out string pbstrVariableContent)
{
    if (pwzVariableName == "selection")
    {
        if (pwzVariableType == "html")
        {
            pbstrVariableContent = "<b>Selection</b>";
        }
        else
        {
            pbstrVariableContent = "Selection";
        }
    }
} 

This code just retrieves an HTML string ("Selection") if the Accelerator requests HTML, or Selection as text otherwise. Note that a real-world implementation would generally need to grab the user selection (whatever form that takes) instead of feeding the platform a dummy variable. For example, a Word add-in might use Application.Selection.Text.

Lastly, we need to implement the IOpenServiceActivityInput::GetType function. It just tells the platform what type of content to expect, which is important for choosing which execution or preview context from the Accelerator ends up being used.

public void GetType(out OpenServiceActivityContentType pType)
{
    pType = OpenServiceActivityContentType.ActivityContentSelection;
} 

In this particular case, we're just returning the selection to be consistent with the other two functions. The code from any given application could also make use of the link and document contexts as well. (See OpenServiceActivityContentType.)

Output

We also need to give the platform a way of presenting Accelerator output to users. To do that, we need to implement the IOpenServiceActivityOutputContext interface. Here's a skeleton definition:

public class MyActivityOutput : IOpenServiceActivityOutputContext
{
    public void Navigate(string pwzUri, string pwzMethod, string pwzHeaders, System.Runtime.InteropServices.ComTypes.IStream pPostData)
    {
        throw new
    }
 
    public void CanNavigate(string pwzUri, string pwzMethod, string pwzHeaders, System.Runtime.InteropServices.ComTypes.IStream pPostData, out bool pfCanNavigate)
    {
        throw new NotImplementedException();
    }
} 

Let's step through both methods. First, we have IOpenServiceActivityOutputContext::CanNavigate. Much as IOpenServiceActivityInput::HasVariable does for input, IOpenServiceActivityOutputContext::CanNavigate tells the platform whether it can navigate to a given URL. Here's a very basic example:

public void CanNavigate(string pwzUri, string pwzMethod, string pwzHeaders, System.Runtime.InteropServices.ComTypes.IStream pPostData, out bool pfCanNavigate)
{
    pfCanNavigate = true;
} 

For the sake of illustration, this method passes true for any URL, but a developer would generally want to perform some URL validation before deploying an application that calls into the Accelerator platform.

The IOpenServiceActivityOutputContext::Navigate method, as one might guess, actually does the work of navigation. This example opens a new browser window and navigates to the specified URL by using any specified post data:

public void Navigate(string pwzUri, string pwzMethod, string pwzHeaders, System.Runtime.InteropServices.ComTypes.IStream pPostData)
{
    SHDocVw.InternetExplorer browser = new SHDocVw.InternetExplorer();
    browser.Visible = true;
 
    object url = pwzUri;
 
    object PostData = null;
 
    if (pwzMethod == "POST")
    {
        // Read the size of the buffer from the first two bytes.
        IntPtr sizelocation = Marshal.AllocHGlobal(4);
        pPostData.Seek(0, 2, sizelocation);
        int size = Marshal.ReadInt32(sizelocation);
        Marshal.FreeHGlobal(sizelocation);
 
        // Reset the stream.
        pPostData.Seek(0, 0, IntPtr.Zero);
 
        byte[] buf = new byte[size];
 
        // Read the complete buffer.
        IntPtr putItHere = Marshal.AllocHGlobal(4);
        pPostData.Read(buf, buf.Length, putItHere);
        int cReadBytes = Marshal.ReadInt32(putItHere);
        Marshal.FreeHGlobal(putItHere);
 
        PostData = buf;
    }
 
    object headers = pwzHeaders;
    object flags = 0;
    object target = "_self";

    browser.Navigate2(ref url, ref flags, ref target, ref PostData, ref headers);
 
} 

You'll probably have to add a reference to ShDocVw.dll to your project to get the above example to work. Underneath, the InternetExplorer class is using the IWebBrowser2 interface, which can also be embedded in form elements for the purposes of preview. Note that developers are not required to use Internet Explorer— in theory, any browser that can be launched programmatically in the fashion above is compatible.

Note that the code example above will create a browser window underneath other windows. To bring it to the foreground, you can use SetForegroundWindow.

Execution

Execution is the main point of Accelerators, but compared to the things covered so far, it's even easier. An Accelerator can be executed by calling its IOpenServiceActivity::Execute method:

act.Execute(new MyActivityInput(), new MyActivityOutput()); 

If an Accelerator is being added to a menu of some sort, a common way of calling IOpenServiceActivity::Execute is to connect a click event handler to the menu item, and then call IOpenServiceActivity::Execute there. This will call the appropriate navigation code when the user clicks an Accelerator. For example, when enumerating the menu, add a click event to the menu item:

menuitem.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(menuitem_Click); 

Then execute the Accelerator in its event handler:

void menuitem_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault)
{
    IOpenServiceActivity act = MenuMapDefault[Ctrl.Index];
    act.Execute(new MyActivityInput(), new MyActivityOutput());
} 

This sequence should work in a variety of implementations.

Preview

Preview really isn't much different from execution. The primary difference is that the UI for preview is generally provided by the host application, as opposed to just launching a new browser window.

This is fairly easy to accomplish by using a Windows Forms application. Just include a form that contains a WebBrowser control, and use IOpenServiceActivityOutputContext::Navigate to make the window visible and navigate the embedded WebBrowser within it. This should work for unmanaged code as well, although it will involve lower-level interaction with COM interfaces.

Just like execution, a developer will probably want to connect preview to an event handler. The MouseHover and MouseLeave events are quite useful for this.

Managing Accelerators

If a developer wants to manage Accelerators (for example, install, uninstall, set defaults, and so on), the platform provides ways to do that.

IOpenServiceManager is an interface that can be used to add or remove Accelerators from the system. For example, this will install an Accelerator:

IOpenService service = null;

man.InstallService("https://www.contoso.com/<tla rid="tla_accel"/>.xml", service); 

Uninstallation is similar, although one first needs to acquire the ID of the Accelerator to uninstall. This can be accomplished by calling IOpenService::GetID or IOpenServiceActivityManager::GetActivityByHomepageAndCategory. For example:

IOpenServiceActivity Activity;

osam.GetActivityByHomepageAndCategory("https://www.contoso.com/<tla rid="tla_accel"/>.xml", "verb", Activity); 

While most applications probably won't have a need to modify Accelerators on the system, the platform management APIs are powerful enough to allow a developer to effectively simulate the Manage Add-ons screen for Accelerators in Internet Explorer 8.