Chapter 3: Component Sharing

 

Windows-based applications can share code, as well as application and component state in the registry, application specific data in the file system, and Windows APIs which expose global namespaces. Sharing allows for the efficient leverage of limited hardware resources and reduces the exposed front that Quality Assurance groups must test.

However, there are costs to sharing. Sharing causes applications to become interdependent upon one other, which introduces an element of fragility. In the extreme, applications that once worked will mysteriously start functioning oddly, or even fail. Typically, an application is dependent on a particular version or implementation of a shared component. If that shared component is upgraded (or downgraded) as a result of installing another application, the former application may break. This condition is often referred to as "DLL Hell."

Successful global sharing requires that any shared component behave exactly like previous versions of that component to avoid this problem. In practice, however, 100% backward compatibility is difficult, if not impossible, to achieve because:

  • It is usually not feasible to test all configurations in which the shared component may be used

  • Component "functionality" is not easily defined

    Applications may become dependent on unintended side effects that are not considered part of the core "functionality" of the component. For example, a component author may choose to fix a "bug" in the component, which another application has unknowingly (or knowingly) become dependent on.

  • The sheer volume of applications that use the component exacerbates the problem

Windows 2000 and Windows 98 Second Edition enable a new form of sharing called side-by-side sharing which minimizes this application fragility. Side-by-side sharing enables multiple versions of the same COM or Win32 component to run at the same time in memory. This means applications can use the specific components that they were designed for and tested with, even if another application requires a different version of the same component. This allows developers to build more reliable applications because developers can choose the version of the component they will use for their application, independent of the other applications on the system.

Side-by-side sharing requires the use of side-by-side components, which are ordinary COM or Win32 components, except that they:

  • Are installed into the application directory rather than the system directory
  • Must be registered properly with the system (as described later in this chapter) so they won’t conflict with other versions of the component that may exist

Side-by-side components are isolated to a specific application and are not globally shared across all applications:

  • A side-by-side component can run safely "side-by-side" with a different version of the same component that is installed elsewhere on the system. If another application on the system requires a different version, your application will be unaffected and both applications will run with their respective versions of the component.
  • In the event another application installs a new version of a component on the system, your version of that component will remain untouched since you installed it into your application directory. Your application will continue to use the same version of the component that you shipped with your application, while the other application will use its version. Both versions can exist in memory at the same time.

The following chart compares the traditional "global sharing" method with side-by-side sharing:

Global Sharing Side-by-Side Sharing
Requires consistency of interface and behavior over time. Backward compatibility needs to be tested. Interface and behavior can change over time.
All versions can/must share the same state (registry, etc). Requires isolation of state by component version and optionally, by application using the component.
Applications all use the "latest version" of a component. Applications choose which version of a component they use.
Installs into a globally shared location, for example the system directory. Installs into the application’s directory.
System supports activation by absolute name (file name in system directory or GUID to an absolute file name). Activation is by relative name based on the application being run.
Upgrading / bug fixing the component simply requires replacing the single globally shared component. Upgrading and bug fixing a component is the responsibility of the application developer.

Customer Benefits

  • Application writers can safely use your components without fear of some other application overwriting them (the main cause of "DLL Hell" is eliminated)
  • Customers can safely install and reliably run multiple applications that require different versions of the same shared component
  • Customers can update applications independently of each other, without fear of impacting existing applications
  • Customers will be able to install your component without requiring a reboot, even if another application is using a different version of your component

Requirements

3.1  On Windows 2000 do not attempt to replace files that are protected by Windows File Protection

3.2  Component producers: Build side-by-side components

3.3  Application developers: Consume and install side-by-side components

3.4  Install any non side-by-side shared files to the correct locations

References

How to Comply with Component Sharing Requirements

3.1  On Windows 2000 do not attempt to replace files that are protected by Windows File Protection

Your application must not attempt to replace any Windows 2000 files that are protected by Windows File Protection (WFP). To ensure that you do not invoke WFP, call SfcIsFileProtected when installing any file you did not create. The Windows Installer service version 1.1 automatically does this for you.

Protected files include the following files that ship on the Windows 2000 CD:

  • Most .SYS, .DLL, .EXE and .OCX files
  • The following fonts: micross.ttf, tahoma.ttf, tahomabd.ttf; fixedsys.fon, dosapp.fon, modern.fon, script.fon and vgaoem.fon

Note   Some redistributable files, such as specific versions of Microsoft Foundation Classes (MFC) DLLs, are installed by Windows 2000 and are protected by WFP.

Protected files form the core of the operating system and it is essential for system stability that the proper versions be maintained. These files can only be updated via Service Packs, operating system upgrades, QFE hotfixes, and Windows Update. Applications cannot replace them, and attempting to replace these files by means other than those listed above will result in the files being restored by the Windows File Protection feature.

Important   If your application requires newer versions of these components, your application must update these components using an approved Microsoft Service Pack that installs the required versions.

About Windows File Protection

Windows File Protection is a feature of Windows 2000 which prevents the replacement of essential system files. WFP runs as a background process on Windows 2000 and monitors the files listed in the preceding section. When WFP detects that a protected file has been changed, it restores the original.

The following code shows how to check if a file (in this case "ntdll.dll") is protected by WFP. Note that SfcIsFileProtected is Unicode-only and requires a fully qualified path.

SHGetFolderPath(NULL,CSIDL_SYSTEM, NULL, 0, szSystemDir);
PathAppend(szSystemDir,"ntdll.dll");
MultiByteToWideChar(CP_ACP, 0, szSystemDir, -1, wzFileName, 265);

if ( SfcIsFileProtected(wzFileName) )
   MessageBox(hWnd,szProtected,szSystemDir,MB_OK);
else
   MessageBox(hWnd,szNotProtected,szSystemDir,MB_OK);

3.2  Component producers: Build side-by-side components

Authors writing new redistributable components must use side-by-side sharing techniques so their components can be installed into the application directory.

"Side-by-side" means version 1 of the component runs in one process while version 2 runs in another process. This requires each version to isolate its state so that it doesn’t conflict with other versions. The rules below will help ensure your components can run in a side-by-side scenario. Most ordinary components will need only minor modifications to meet these requirements.

To build a side-side component:

  • Do not register full path names for COM registrations; the operating system will find any dependent components in the application directory

    • If COM meta data (such as threading model) changes between versions, expose a new GUID in the new version
    • Typelibs must be contained within your DLL instead of as a separate file; use LoadTypeLib and not LoadRegTypeLib to load typelibs
    • You must reference count GUIDs: uninstall of a side-by-side component should not remove its GUIDs from the registry because another app on the system using this component could be dependent on it
  • Registry settings must be per version; for example, store your registry state in keys with the following naming convention

    HKCU\MyCompany\MyComponent\VersionXXXX\
    
  • Other global stores must be per version if the data changes across versions:
    • Use of shared data structures like memory mapped files, mutexes, and named pipes needs to be designed to be different for each version of the component so that different versions can run at the same time
    • Use of memory mapped files and shared files must either be separated per component version or have version keys within the file so that different versions of the component can access them at the same time

Side-by-side sharing is supported in Windows 2000 and Windows 98 Second Edition. For previous Windows operating systems, side-by-side activation is not supported. However, a side-by-side DLL can be written so that it installs on down-level platforms into the system directory, and functions in a global-sharing (backward compatible) fashion. Side-by-side DLLs must be installed to the system directory on these platforms, and in the application directory on Windows 2000 and Windows 98 Second Edition. Applications must dynamically check the operating system version to determine which sharing technique to use. The following code checks if side-by-side sharing is supported:

BOOL bPlatformSupportsSideBySide(void)
{
    OSVERSIONINFOEX osviex ;

    osviex.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    // If platform does not support OSVERSIONINFOEX structure, it does not 
    // have side-by-side support
    //
    if (!GetVersionEx((OSVERSIONINFO *)&osviex))
    {
        return FALSE ; // no DLL re-direction
    }

    // However, for NT, then NT4 SP4 had support for OSVERSIONINFOEX 
    // support but it did not support DLL re-direction. 
    // If DLL re-direction appears in a future NT4 SP,  this code will have 
    // to be updated.
    //
    if ( (osviex.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osviex.dwMajorVersion < 5) )
    {
        return FALSE ;
    }

    // For the other platform Ids, assume has side by side support
    return TRUE ;
}

3.3  Application developers: Consume and install side-by-side components

For Windows 2000 and Windows 98 Second Edition, any side-by-side DLLs that your application depends on must be installed into your application directory:

%ProgramFiles%\<company name>\<App Name>

Be sure to follow the install and uninstall instructions provided by the component developer.

On down-level operating systems, install side-by-side components into the system directory. Installers must dynamically check the operating system version to determine which sharing technique to use.

3.4  Install any non side-by-side shared files to the correct locations

The proper location for shared components depends on whether these components are shared across companies or by a single company.

  • Shared components that are private to a single software vendor must be installed in one of two places. Do not store these files in the system directory

    common files directory\<company name>
    %ProgramFiles%\<company name>\Shared Files
    

    The common files directory can be accessed by passing CSIDL_PROGRAM_FILES_COMMON to the SHGetFolderPath API, or by using the Windows Installer CommonFilesFolder property. For more information on using Windows Installer Properties, see the Windows Installer Programmer’s Reference in the Platform SDK.

  • Non-side-by-side OCXs and DLLs that are shared by multiple software vendors can be placed in the system directory to ensure backward compatibility with those applications

    Important   You must document in your Vendor Questionnaire any cases where your software application writes to the system directory.

  • New control panel applets (CPLs) must be installed in the application directory on Windows 2000:

    Register the path by adding a value under either of the following registry keys:

    HKLM\software\microsoft\windows\CurrentVersion\control panel\cpls 
    HKCU\software\microsoft\windows\CurrentVersion\control panel\cpls 
    

    Example of a name / value pair under this key:

    MyCpl = "%ProgramFiles%\MyCorp\MyApp\MyCpl.cpl"
    
  • Services and device drivers must be placed in the system directory

**Note   **The system directory is not locked down when the user is a power user or administrator, so legacy components or globally shared components can still be placed there. However, Windows File Protection prevents the replacement of protected operating system files. See Requirement 3.1.

How to Pretest Applications for Component Sharing Requirements

To Pretest Installation

After installing your component, make sure there are no instances of fully qualified names to your component stored in the registry.

To Pretest Uninstall

Test uninstall with one instance of your component installed (only one application uses your component). Make sure all GUIDs and other information to your component have been removed.

Test uninstall with two instances of your component installed (i.e., two apps uses your component). Make sure the other application still runs after uninstalling the first application which uses your component.

To Pretest Co-existence

Install your component in two different applications. Run both applications at the same time and make sure your component continues to function properly. Ideally, you will test using two different versions of your component.