August 1998
Implementing a Web View Namespace Extension Using Active Directory Services
Download Aug98ADSIView.exe (193KB)
Todd Daniell works in the Internet and Systems Securities Lab at Hewlett Packard. Brian Daigle, Doug Bahr, and Dave Mims are members of the DocuPACT development team at InterTech Information Management, Inc. Send questions
about this article to adsiview@intertech.com.
|
Have you ever
wondered how to implement a Web View in a namespace extension? We did, too. We'll show you how to create a namespace extension using the Active Template Libraries (ATL) to implement a Web View. Our sample presents the Active Directory Services as a namespace using the
Active Directory Services Interface (ADSI). But only a minimal knowledge of ADSI is required to follow the discussion. In addition, we will demonstrate how to enable customization of your Web View using a template Web page.
The sample code included here is designed to run on Windows® 95 with Internet Explorer 4.01 (or greater), Windows NT® 4.0 Service Pack 3 with Internet Explorer 4.01 (or greater), Windows 98, or Windows NT 5.0 beta 1. We built the sample code using Visual C++ 5.0 (Service Pack 3), the Platform SDK, the ADSI SDK, and the Internet Client SDK. The Visual C++® 5.0 Service Pack can be downloaded from http://www.microsoft.com/visualc. The Platform SDK, the ADSI SDK, and the Internet Client SDK can be downloaded from http://www.microsoft.com/msdn. Before running the sample, you must install the ADSI release 2.0 runtime environment, which can be found at http://backoffice.microsoft.com/downtrial/moreinfo/adsi2.asp.
Namespace Extension Basics
Using the IShellFolder Interface
Designing the PIDL
|
Figure 4 Types of PIDLs |
|
|
You cannot assume that the PIDL passed in is a simple PIDL. If it is a complex PIDL, you must be able to traverse the lists of IDs and compute the offset from the start of the PIDL to your title.
Some methods are currently documented as taking only simple PIDLs and some are documented as taking both. If you assume that all methods take complex PIDLs and write a routine to run the PIDL that returns the last valid ID, then you do not have to worry about which methods take simple or complex PIDLs. By simply assuming that all methods take a complex PIDL, you should have future compatibility with any changes that Microsoft might implement. Another optimization that could be contained within the ID is the attributes that Windows Explorer wants on the object. Explorer calls IShellFolder::GetAttributesOf to obtain this information. These attributes disclose things like whether the object is a folder, has children, and can be moved, copied, renamed, deleted, and so on. Figure 6 lists the most common attributes of an object. Because this information usually does not change, it is a good idea to store it within the ID. Implementing the strings inside the ID posed a problem. Both the display name and ADSI path are variable in length, so they could not be defined inside the ID structure. This made implementing the ID a little more challenging. To overcome the problem, the ADSI ID is not a single structure, but rather two structures. When Windows Explorer needs to open another level in the tree control it will call two functions. First it must enumerate the list of children from the namespace. To do this, it will call IShellFolder::EnumObjects passing in flags that indicate the types of objects it wants. This method returns an enumerator object called IEnumIDList that list the PIDLs for the children of the folder. To actually get the object represented by the PIDL, Windows Explorer will call IShellFolder::BindToObject. You will need to make sure that the PIDL represents an object with the interface that Explorer is requesting. Generally, this method will be called when Explorer needs another IShellFolder object representing a sublevel of folders for the tree.
Setting Up the User Interface
Implementing the Classic View
The functionality of the list view is exposed from the CListView32 API. The API is somewhat grid centric by exposing row and column-based methods. There is also basic support for image lists. As icons are retrieved from the associated folder, they are inserted into the image list of the list view. If the icon has already been cached, its associated image index is used instead of duplicating the icon in the image list. So that the CListView32 can be reusable, an optional callback class called CListView32Events has been implemented. This allows CListView32 to be unspecific to our shell implementation. If an object inherits from CListView32Events and is registered on the constructor of a CListView32 object, the CListView32Events-derived object will receive events from the CListView32 object. This is what was done with the Classic View. CADSIClassicView inherits from CListView32Events and registers itself with its member CListView32 object, so that it can receive events from the CListView32 object. Now that we have covered CListView32, let's move on to the shell view interfaces. We will implement only the basic and required methods of the shell view interfaces for our sample. To accomplish this we have to associate a shell view with its shell folder, create the view, provide the view window, be able to refresh the view, track and supply the associated folder settings, and destroy the view. In addition, we will need to have support for inserting menu items and toolbars into Windows Explorer. Let's examine the relationships of the various view classes. The implementation of IShellView and IShellView2 are in CADSIShellView. CADSIShellView creates CADSIDefView, which is called our Default View. The Default View supplies the window handle that is returned to Windows Explorer. It also propagates interface calls to either the Classic View or the Web View. Next, we will discuss the Classic View implementation that namespace extensions currently support (see Figure 10). Later when we cover the Web View, we will discuss the necessity of the Default View. To simplify issues, the Classic View will be presented as if it were called directly from the shell view interface.
The implementation of our shell view, called CADSIShellView, does not inherit from CComCoClass nor does it have an entry in the ATL BEGIN_OBJECT_MAP. This is because it is not cocreatable. Instead, it is only instantiated from a call to IShellFolder::CreateViewObject. But the Classic View that ultimately handles calls from CADSIShellView is cocreatable. As a result, CADSIClassicView inherits from CComCoClass and has an entry in the ATL BEGIN_OBJECT_MAP. This is necessary so the view can be cocreated from the DHTML page during the Web View. The Classic View will no longer be requested solely from IShellFolder::CreateViewObject. We'll discuss this in more detail later. The CComCreator and CComObject templates instantiate the shell view to return from CADSIShellFolder::CreateViewObject. This is the same mechanism that ATL uses when handling requests from COM clients to cocreate an object. If an attempt to use "new" to instantiate the custom shell view interface had been made instead, it would result in "Cannot instantiate abstract class (error C2259) due to the IUnknown interface methods." The CComObject template implements the IUnknown interface methods and propagates those calls on to internal implementations provided by the CComObjectRoot template. The Classic View interface inherits from the CComObjectRoot template. The CComCreator template is a utility that instantiates and initializes the COM object, then queries the object for the specified interface to return. The list view is created via the call to IShellView::CreateViewWindow. The call supplies the previous shell view object. Microsoft documents that this parameter may be NULL, so never assume that it is valid. It provides optimized browsing between similar views. We did not take advantage of that in our sample. The folder settings specify attributes of how to create the view window and we used it in creating our view. Folder settings are also cached for calls made to IShellView::GetCurrentInfo. We tracked our own reference to the shell browser so we can communicate with Windows Explorer (don't forget about the rules regarding in and out parameters). An example of this will be discussed when we use the shell browser object to browse into a folder of the list view. The final two parameters denote the dimensions for the shell view and an out parameter that is updated with the window handle of our view. Before the view window is actually created, we must obtain the window handle associated with the shell browser object. This will be used as the parent window. Then we determine how the view should be created based on the shell folder settings. Finally, we initialize and set the list view with data based on the ADSI folder selected in the tree view. The ADSI folder is the parent folder of the shell view that was supplied when the view was created in IShellFolder::CreateViewObject. Once the ADSI COM object associated with the shell folder is obtained by supplying our PIDL helper with its associated ADSI path, the sub ADSI objects of the shell folder are enumerated and inserted into the list view. The list view displays the consistent properties that are valid for all ADSI objects. These include name, path, guid, class, schema, and parent path. Note that depending on the ADSI object other unique properties can exist and be queried based on the ADSI schema. Just like the My Computer namespace extension, our sample can browse into a folder when a user double clicks a folder in the list view. The folder is then selected in the tree view and its children are displayed in the list view. As we mentioned before, the ADSI shell view has been registered with its list view to receive control events. Of particular interest here is the NM_DBLCLK notification that is handled by CADSIClassicView::OnDblClick. We first get the ADSI path associated with the selected item from the list view. Remember, the path is one of the attributes consistent across all ADSI objects and is displayed in the list view. Then, we use our ADSI PIDL helper to build a PIDL based on the path. If it is not a container of other ADSI objects, the action is ignored. Otherwise, we use our cached shell browser object and invoke its BrowseObject method with the PIDL and the default browser flag. Lastly, we clean up our PIDL. Two other methods that need to be implemented are IShellView::GetWindow and IShellView::Refresh. These interface methods will be called when the user requests a refresh of the view by selecting the F5 key. We should return the window handle of the list view from the call to GetWindow. For Refresh, we clear the data in the list view control and retrieve the values again from the ADSI objects as we did when the shell view was first created. The shell view returns the current folder settings associated with the list view from calls to IShellView::GetCurrentInfo. Remember that the initial folder settings were supplied in the call to IShellView::CreateViewWindow. GetCurrentInfo used along with CreateWindowView allows the Windows Explorer to maintain a somewhat consistent view of data across different namespace extensions. Implementation of this method is required, not because of Explorer, but because of problems seen with the My Computer namespace extension. Before this method was implemented and it returned E_NOTIMPL, the My Computer namespace extension would fail to create its list view or update its menus correctly when moving from the ADSI sample namespace extension to the My Computer namespace extension. After the view of the My Computer namespace extension would fail to create and we moved back to the ADSI sample namespace, the view mode for the folder settings of the previous view supplied in CreateWindowView would have a value of zero, which is not a valid value. Valid values should be between FVM_ICON and FVM_DETAILS. Lastly, we must close and clean up any resources we have associated with the view. This is expected when IShellView:: DestroyViewWindow is invoked. In the DestroyViewWindow method, we flush the list view and destroy all associated windows. When the shell view is finally destroyed, it will release its ownership to the shell browser.
Implementing the Web View
The most difficult obstacle in researching the Web View was figuring out how the Web View and the Classic View would communicate. The key was understanding the design of DHTML and the Document Object Model (DOM). The DOM is defined by groups of collections. There are different types of collections contained within each DHTML document, and each collection type has a different set of information that can be obtained from the document or changed in the document. There are a large number of interfaces that allow access to DHTML DOM. These interfaces are typically named "IHTMLxxxx." They are grouped based upon the collection and element types within the document. All of the interfaces are located in the MSHTML.DLL and are described in the Internet Client SDK documentation. The key is understanding that the DHTML document can be accessed from the IWebBrowser2 interface. As a result, the host container of the WebBrowser control, the scriptlets within the HTML page, and the ActiveX controls loaded within the page have access to the DHTML DOM interfaces using COM. By enumerating the elements within the document, our code can find an element of the OBJECT tag type. Then it examines the element using the IHTMLElement interface and gets the IHTMLObjectElement interface. From the IHTMLObjectElement interface, we compare the CLSID of the object to make sure it is the same as the Classic View object's CLSID. We can then get an IDispatch pointer to the Classic View instance. Now we have the communication necessary to complete the creation of the Classic View within the Web View. Whew! Just after calling the Navigate2 method to instruct the WebBrowser control to load our Web page, we walk the DHTML DOM to locate the Classic View object. A problem that we ran into was that the Classic View control may not be loaded when the WebBrowser control returns from the Navigate2 call. Avoiding this would require knowing when the WebBrowser control has completed document loading before we actually create the Classic View for a specified folder location. We later determined that WebBrowser supports calling a connection point to receive events. This required the Web View to support the DWebBrowserEvents2 dual interface. The events interface notifies the sink when events occur. We are primarily concerned when the document has been fully loaded. The DocumentComplete event is received for each document that is loaded. Because the DHTML DOM allows documents within documents, we must wait for the outer document to completely load. We know the entire document is loaded when the DocumentComplete event has the IWebBrowser2 interface equal to the IDispatch pointer of the document. One of the parameters of the DocumentComplete event is the IDispatch pointer of the completed document. Now that we have loaded the DHTML document, we can examine all of the elements in the document for our Classic View object. The connection is made to the Classic View by calling the CreateView method. The information necessary to create the list view within the currently empty Classic View control is passed to the CreateView method. We also wanted to search and replace some of the text within the DHTML script so that the current path is displayed on the Web page. Searching for the text is similar to getting the Classic View IDispatch pointer, with one exception. When we examine every element, we look at the inner text of the DHTML script and parse it for our ADSI path parameter called %THISADSIPATH%. We replace the parameter text with our ADSI path that we get from the created Classic View. Then, we place the updated text back into the DHTML document. Finally, the Web View is visible with the Classic View fully initialized and accessible to the user.
Toolbars and Menus
Conclusion
From the August 1998 issue of Microsoft Systems Journal..
|