Under The Hood

Matt Pietrek

Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at Nu-Mega Technologies Inc., and can be reached at 71774.362@compuserve.com.

QI noticed the MicrosoftÒ Office for WindowsÒ 95 installation installed a program called FASTBOOT in my startup group. Since it was put there without my permission, I took a look at it. It seems to do a small bit of OLE, then ends. What is FASTBOOT really doing, and why?

John Robbins

AThe short answer seems to be that FASTBOOT does nothing. But it must be there for some reason, and I think we can deduce what it tries to accomplish. Since the code certainly goes through a lot of OLE gyrations, I'll assume that you're at least passingly familiar with basic OLE concepts.

Figure 1 gives a pseudocode version of FASTBOOT, which was most likely written in C++ without MFC. Normally, programs that use OLE are written with MFC. However, since the name FASTBOOT implies that it gets things running quickly, the program shouldn't have to load and initialize the MFC framework.

Figure 1 FASTBOOT.CPP


 #include <windows.h>
#include <ole2.h>

DEFINE_GUID( Fastboot_CLSID, 0xFEF72D70, 0x6DEB, 0x11CE,
             0xaa, 0x0e, 0x00, 0x80, 0x29, 0x00, 0x3c, 0x66 );
                
WinMain( HANDLE hInstance, HANDLE hPrevInstance,
         LPSTR lpszCmdLine, int nCmdShow )
{
    HWND hWndDragDrop;
    DWORD dwRegister;   // Token returned by RegisterClassObject [ESP+1Ch]
    DWORD dwRegister2;  // Token returned by IRunningObjectTable::Register
    LPSTORAGE pIStorage;
    LPSTREAM pIStream;
    LPMONIKER pIMoniker;
    LPRUNNINGOBJECTTABLE pIRunningObjectTable;
    LPDROPTARGET pIDropTarget;
    HRESULTS hResult;

    if ( 1 != RegisterClassAndCreateWindow( &hWndDragDrop ) )
        exit( 1 );

    hResult = OleInitialize( 0 );

    if ( S_OK == hResult )
    {
        // Interesting hardcoding of the file name
        hResult = StgCreateDocFile( TEXT("c:\temp\~oleapp.doc"),
                                    STGM_TRANSACTED | STGM_CREATE |
                                    STGM_SHARE_DENY_WRITE | STGM_READWRITE,
                                    0, &pIStorage )
        if ( S_OK == hResult )
        {
            hResult = pIStorage->CreateStream( TEXT("DATA"),
                            STGM_CREATE | STGM_SHARE_EXLUSIVE STGM_READWRITE,
                            0, 0, &pIStream );
                        
            if ( S_OK == hResult )
            {
                hResult = CreateFileMoniker( TEXT("c:\temp\~oleapp.doc"),
                                             &pIMoniker );
                if ( S_OK == hResult )
                {
                    hResult = GetRunningObjectTable(0, &pIRunningObjectTable);
                    
                    if ( S_OK == hResult )
                    {
                        pIDropTarget = 0;
                        pIDropTarget = new DropTarget;
                        
                        hResult = IRunningObjectTable->Register(
                                    0, pIDropTarget, pIMoniker, &dwRegister2);

                        if ( S_OK == hResult )
                        {
                            // Interesting how an IDropTarget is passed,
                            // rather than an IClassFactory
                            hResult = CoRegisterClassObject( Fastboot_CLSID,
                                            pIDropTarget, CLSCTX_LOCAL_SERVER,
                                            REGCLS_MULTIPLEUSE, &dwRegister );
                                        
                            if ( S_OK == hResult )
                            {
                                hResult = RegisterDragDrop( hWndDragDrop,
                                                            pIDropTarget );
                                if ( S_OK == hResult )
                                {
                                    // Get rid of the IDropTarget
                                    hResult = RevokeDragDrop( hWndDragDrop );
                                    if ( S_OK != hResult )
                                        exit( 1 );
                                }

                                if (S_OK != CoRevokeClassObject( dwRegister ))
                                    exit( 1 );
                            }
    
                            hResult=pIRunningObjectTable->Revoke(dwRegister2);
                            if ( S_OK != hResult )
                                exit( 1 );
                        }

                        PIRunningObjectTable->Release();
                    }

                    pIMoniker->Release();
                }

                pIStream->Release();
            }

            pIStorage->Release();
        }

        OleUninitialize();
    }

    DestroyWindowAndUnregister( hWndDragDrop );

    return 0;
}

//
// A very minimal IDropTarget interface implementation
//

IDropTarget::QueryInterface( REFIID refiid,
                             void** ppvObject )
{
    *ppvObject = 0;

    if ( !IsEqualIID( IID_IUnknown, refiid ) )
        if ( !IsEqualIID( IID_IUnknown, refiid ) )  // Hey!  Identical test!
            return E_NOINTERFACE;

    // If we get here, IID_IUnknown was asked for...
    this->AddRef();
    *ppvObject = this;

    return S_OK;
}

IDropTarget::AddRef(void)
{
    InterlockedIncrement( &this->m_cRef );
    return this->m_cRef;
}

IDropTarget::Release(void)
{
    if ( 0 == InterlockedDecrement( &this->m_cRef ) )
    {
        // If we got here, the reference count dropped to zero...
        if ( this )
            delete this;
        
        return S_OK;
    }

    return this->m_cRef;
}

IDropTarget::DragEnter( IDataObject * pDataObject,
                        DWORD grfKeyState,
                        POINTL pt,
                        DWORD * pdwEffect )
{
    *pdwEffect = 0;
    return S_OK;
}

IDropTarget::DragOver( DWORD grfKeyState,
                       POINTL pt,
                       DWORD * pdwEffect )
{
    pdwEffect = 0;
    return S_OK;
}

IDropTarget::DragLeave(void)
{
    return S_OK;
}

IDropTarget::Drop( IDataObject * pDataObject,
                   DWORD grfKeyState,
                   POINTL pt,
                   DWORD * pdwEffect )
{
    pdwEffect = 0;
    return E_FAIL;
}

//
// Functions to register/unregister the OleInit window class, and
// create/destroy a window of that class.  Called by WinMain.
//
RegisterClassAndCreateWindow( HWND * pHWnd )
{
    HMODULE hModule;
    WNDCLASS wndClass;
    HWND hWndRet;
    
    hModule = GetModuleHandle( 0 );
    
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = 0;
    wndClass.hIcon = 0;
    wndClass.hCursor = 0;

    wndClass.lpszMenuName = "OleInit";
    wndClass.lpszClassName = "OleInit";

    wndClass.style = CS_VREDRAW | CS_HREDRAW;
    wndClass.lpfnWndProc = FastBootWndProc;

    wndClass.hbrbackground = COLOR_WINDOW + 1;  // that is, 6
    
    if ( 0 == RegisterClass( &wndClass ) )
        return 0;
    
    hWndRet =
        CreateWindowEx( 0, "OleInit", "OleInit", WS_OVERLAPPEDWINDOW,
                        0x80000000, 0,      // X, Y
                        0x80000000, 0,      // nWidth, nHeight
                        0, 0, hModule, 0 ); // parent, menu, hModule, lParam
    if ( hWndRet )
        pHWnd = hWndRet;
    
    return hWndRet ? TRUE : FALSE;
}

DestroyWindowAndUnregister( HWND hWnd )
{
    HMODULE hModule = GetModuleHandle( 0 );

    DestroyWindow( hWnd );
    UnregisterClass( "OleInit", hModule );
}

FastbootWndProc( HWND hWnd,
                 UINT uMsg,
                 WPARAM wParam, 
                 LPARAM lParam )
{
    switch ( uMsg )
    {
        default:
            return DefWindowProc( hWnd, uMsg, wParam, lParam );

        case WM_DESTROY:
            PostQuitMessage( 0 );
            return 0;

        case WM_COMMAND:
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
    }
}

In the following description I'm assuming that each API or OLE method call succeeds. However, at any point, a call could fail and FASTBOOT would handle it gracefully. All of the code is within conditional blocks that execute only if everything has gone OK so far. That's why the listing has so much indentation in it. By assuming that each API or OLE method call succeeds, I can keep the explanation relatively simple.

At the start of WinMain, FASTBOOT calls a function that registers a windows class named OleInit, then creates a window of that class. This window will be used later by some drag-and-drop related code. Interestingly, no messages are ever pumped for this window, nor does it ever appear on the screen. There's no GetMessage or PeekMessage loop anywhere in the FASTBOOT code; this is the first clue that FASTBOOT isn't your typical program. After creating the window, WinMain calls OleInitialize. Nothing too exciting here.

Next, WinMain creates a structured storage file called "c:\temp\~oleapp.doc". Within ~oleapp.doc, FASTBOOT creates a "data" stream via IStorage::CreateStream. Strangely, nothing is ever written to this stream—yet another clue that FASTBOOT is an unusual program. Once the ~oleapp.doc stream is present, WinMain calls CreateFileMoniker to create an IMoniker instance that identifies the stream.

Putting the ~oleapp.doc stream aside for a moment, WinMain continues by using GetRunningObjectTable to obtain an IRunningObjectTable interface pointer. Next, the code uses operator new to create a DropTarget object that it refers to using the IDropTarget interface. All of the IDropTarget interface methods are implemented immediately following WinMain in the FASTBOOT code.

The IDropTarget methods are very simple dummy stub functions. FASTBOOT's WinMain has three things planned for it. First, the code registers the IDropTarget pointer with the running object table via IRunningObjectTable::Register. To label the registered interface, WinMain uses the previously obtained IMoniker object that refers to ~oleapp.doc. Second, WinMain gives the IDropTarget object to CoRegisterClassObject. This theoretically makes FASTBOOT an OLE local server, ready to serve up IDropTarget objects to anybody who wants one. Third, the IDropTarget object becomes a parameter to RegisterDragDrop, which associates the window created at the beginning of WinMain with the IDropTarget object.

At this point in the FASTBOOT code, there's quite a bit of OLE cranked up. WinMain has pointers to instances of IStream, IStorage, IMoniker, IRunningObjectTable, and IDropTarget interfaces.

The rest of the WinMain code simply reverses everything that's been done so far. You heard right! WinMain calls RevokeDragDrop, follows it with a call to CoRevokeClassObject, then invokes IRunningObjectTable::Revoke. These three actions have the cumulative effect of removing the IDropTarget object from memory. The rest of WinMain includes calls to the Release methods for IRunningObjectTable, IMoniker, IStream, and IStorage, removing all these object instances from memory. WinMain calls OleUninitialize, indicating that it's finished playing with OLE, destroys the window created earlier, and unregisters the OleInit window class. So much for WinMain.

The next section of code implements a dummy IDropTarget interface. It's obvious that this IDropTarget was never intended to be used as a true drop target; all but one of the non-IUnknown methods tell the caller that they can't accept dropped data.

Two functions that deal with window classes and windows follow IDropTarget. One registers a window class and creates a window of that class, the other reverses the process. Pretty standard stuff.

The remainder of the FASTBOOT pseudocode is an elementary window procedure. The only message not handled by DefWindowProc is WM_DESTROY. Its handler simply invokes PostQuitMessage—a typical thing to do. It shouldn't be surprising that this window procedure is so simple once you remember that FASTBOOT never shows the window or pumps any messages through it (other than standard messages generated inside the CreateWindow and DestroyWindow functions).

Two places in the code warrant further discussion. The first thing that caught my eye was the call to StgCreateDocFile. FASTBOOT passes the literal Unicode string


 "c:\temp\~oleapp.doc"

as the first parameter. Obviously, there's going to be some sort of problem if you don't have a c:\temp directory on your system. Specifically, the StgCreateDocFile call should fail, and the rest of the code I described would be skipped. To test this, I temporarily renamed my c:\temp directory and stepped through FASTBOOT in a debugger. As I expected, FASTBOOT bailed out early. This is most likely just a coding error, as the code could easily create the ~oleapp.doc storage file in the location specified by the TEMP= environment variable.

The other anomaly in FASTBOOT is the call to CoRegisterClassObject. Usually, an application using OLE would register an IClassFactory or IClassFactory2 interface. It's hard to fathom why FASTBOOT registers an IDropTarget interface instead. After all, registering an IClassFactory interface allows client applications to create new OLE object instances, which you can't do with IDropTarget. On the other hand, this is a moot point since FASTBOOT revokes IDropTarget shortly after registering it.

So what exactly is FASTBOOT doing? I checked with several OLE experts, including Bernard McCoy (formerly of GE Fanuc), and our general impression is this: all of the OLE functions and interfaces that FASTBOOT uses are similar to those used by a typical Microsoft Office application. For instance, Word uses IStorage and IStream interfaces to create its DOC files. Likewise, Microsoft Excel registers objects with the running object table. In a sense, FASTBOOT resembles a miniature Office application that does nothing. When FASTBOOT terminates, both FASTBOOT.EXE and OLE32.DLL are unloaded from memory. However, as FASTBOOT quickly dances through its various OLE calls and interface methods, it causes various portions of the OLE DLLs to be paged in from disk (if they're not already present). Thus, at the simplest level, FASTBOOT seems to be priming the disk cache in Windows 95 or the file cache in Windows NTÒ.

To better understand the effect FASTBOOT has on priming OLE32.DLL, I ran the Windows NT PFMON program that comes with the Win32Ò SDK. PFMON monitors the number of hard and soft page faults that occur during a running process. A hard page fault is a regular page fault where the system must do things like find physical memory for the new page and possibly read the page in from a disk file. A soft fault is a page fault where Windows NT remembers that it recently had a given page of memory loaded, and that the page might still be present in physical memory. If so, the system simply moves the page back into the in-use list without much overhead. For instance, the hard disk won't be hit for a soft fault.

The first time I ran FASTBOOT, there were 16 hard page faults in OLE32.DLL and 38 soft faults. After running FASTBOOT again, there were 5 hard faults and 49 soft faults. This means 11 hard page faults were converted to soft faults. Soft faults are handled much faster than hard faults, so you can see how OLE32.DLL executes faster the second time it's used. Typically, OLE32.DLL will be used the second time when you load an Office 95 program or any other OLE-intensive application.

The fact that FASTBOOT is placed in the startup group indicates that it acts as an OLE pump primer. You can remove FASTBOOT without affecting the Office applications. If you combine the time that it takes to run FASTBOOT with the time to start up an Office program, like Word, you won't see any net decrease in load time. However, FASTBOOT takes some of the time needed to fire up OLE and tacks it onto the initial Windows boot time. The result is that, when you actually run an Office application (or any OLE-intensive application), it loads faster than if FASTBOOT hadn't already touched various portions of OLE.

Have a question about programming in Windows? You can mail it directly to Under The Hood, Microsoft Systems Journal, 825 Eighth Avenue, 18th Floor, New York, New York 10019, or send it to MSJ (re: Under The Hood) via:


Internet

Matt Pietrik
71774.362@compuserve.com


This article is reproduced from Microsoft Systems Journal. Copyright © 1995 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., or (303) 447-9330 in all other countries. For other inquiries, call (415) 358-9500.