Author: Suresh Duddi

## XPCOM Dynamic Component Registration

Suresh Duddi <dp@netscape.com>

Dynamic object registration in XPCOM is achieved by interaction of the following components:

- The Component Registry
- The Repository
- The Service Manager
- Component dll implementing `NSRegisterSelf()`

The registration mechanism for XPCOM components is similar in many ways to that of COM. The XPCOM component dlls will have the opportunity to register themselves with the registry. The exact time of installation would be either at install time or as a result of autodetection by the Repository Manager at runtime.

### The Component Registry

There are three types of Component Registries:

1. App-Component-Registry
   Each application has its own Component Registry that lives along with the app in its <exe-dir>/components directory. The Component Registry is created on installation or first run. It is be used read-only by XPCOM
2. User-Component-Registry
Each user can install additional components under their user home directory. These components will be shared across all XPCOM applications that the user runs.
3. Meta-Component-Registry
Sharing Components between application: This can happen in two ways. The installer of an application can find the other application's components by looking for the application specifically and registering them with this app's component registry. The second and more preferable approach is to keep a machine wide Meta-Components-Registry that would aggregate all the app component registries.

The difference component registries will be searched in the following order:

1. User Components Registry
2. App Component Registry
3. Meta Component Registry

The user component registry is the only one that will be updated on the fly by XPCOM. JS will be given the option to update either the App-Component-Registry or the User-Component-Registry and this may succeed fail based on write permission, although the general guideline is to update the User-Component-Registry. JS will have to do special things to update the App-Component-Registry.

Profiles are a notion of the app (navigator) and xpcom has nothing to do with it. The app will store app specific data in a Data-Registry that will be stored under the user's home directory.

#### How does this Solve our problems

1. Multiple installations of mozilla and xpcom based apps
Since each installation is going run with their own App-Component-Registry, basically both apps will work. No inter process locking is essential. Since both processes will operate on the User-Component-Registry, inter process locking of the User-Component-Registry will be required.
2. Third Party components installation
Third parties can install components in their own directories and update the App-Component-Registry (preferable) or User-Component-Registry depending on if the sharing of component needs to be specific to the user or for all users. Facilities for updating the registry would be to use JS or write XPCOM code in their installer. The other option would be to add their components in their own directory, create a App-Component-Registry of their own in their directory and reference it in the Global Meta-Components-Registry. This will get their components used by all applications.
3. Registry used to store app specific data
This is a totally separate registry: the Data-Registry. The theory is that this will reside in the user's home directory. The registry hierarchy is app specific.
4. User Specific components
This is basically the User-Component-Registry. Inter process locking is required as all processes with XPCOM will access the same User-Component-Registry.
5. Embedding
This is requires more thought. The fact is when say Gecko is embedded into an application, Gecko is running most probably in the process space of that application and hence the XPCOM used will look for components in this embedding applications directory. The embedding procedure should create a App-Component-Reqistry for the embedding application that should contain all the components from different apps this app would like to use. This is however not required, if the Meta-Component-Registry exists.
6. User not having permission to the place where the global registry lives, if there is one.
First the App-Component-Registry is written to only when there is a new component or a component has gone away. New components come with installers or the user calls regFactory.exe with the dll as an argument or clicks on a button that says "refresh my user components" which will cause autoregistration of user components. For deleted app components, annotations will be made in the User-Component-Registry. Deleted user components is a non-issue.
7. NFS mounted home directories and app directories
NFS mounted home directories requires inter-machine locking of the User-Component-Registry. NFS mounted app directories dont have a problem as the App-Component-Registry is only used read-only by XPCOM.

So in summary,

- App-Component-Registry pretty much solves the top problem of Multiple applications. With some help from the installer, Third party components will also be solved.
- User-Component-Registry solves the User Specific Components problem.
- Meta-Component-Registry enables the dynamic sharing of components between apps which eases embedding.

As a first cut, I am going to implement the App-Component-Registry for M8.

#### Hierarchy Used by Component Registry

XPCOM uses the nsRegistry to store mappings between CLSIDs and their implementations. The Registry provides persistent storage of hierarchical keys and name-value pairs associated with each key. Each key also keeps a default value.

XPCOM will use the following registry hierarchy:

> `ROOTKEY_COMMON`
> `Common`
> `Classes`
> `CLSID`
> `{108d75a0-bab5-11d2-96c4-0060b0fb9956}`
> `InprocServer (S) = /home/dp/dist/bin/components/libnfs-protocol.so`
> `ProgID (S) = component://netscape/network-protocol&type=nfs`
> `ClassName (S) = NFS Protocol Handler`

> `component://netscape/network-protocol&type=nfs`
> `CLSID (S) = {108d75a0-bab5-11d2-96c4-0060b0fb9956}`

> `Software`
> `Netscape`
> `XPCOM`
> `VersionString (S) = alpha0.20`

> `/home/dp/dist/bin/components/libnfs-protocol.so`
> `ComponentsCount (Int) = 1`
> `FileSize (Int) = 78965`
> `LastModTimeStamp (S) = Wed Feb 24 11:24:06 PST 1999`

> `Events`
> `Startup`
> `{108d75a0-bab5-11d2-96c4-0060b0fb9956}`
> `{17894983-ab78-8d75-a0bb-511d296c4006}`

> `Shutdown`
> `{748958ea-abab-511d-296c-40060b0fb995}`
> `{45617894-983a-b788-d75a-0bab11d296c4}`

###

The Repository: Object instance creation

All object creation happens via The Repository. `nsIRepository::CreateInstance()` will be the primary way of creation of object instances. The steps in instantiation of an object that implements the IID interface and of class CLSID is as follows:

1. The CLSID of the component that would need to create the object instance is identified.

Callers use `nsIRepository::ProgIDToCLSID()` to convert the ProgID string to the CLSID.

2. Load the dll associated with the CLSID after consulting the Registry
3. Instantiate the class factory by calling a globally exported dll function `NSGetFactory()`. This returns an instance of the class factory that implements the `nsIFactory` interface.
4. The actual object creation is delegated to this `nsIFactory` instance with a call to `nsIFactory::CreateInstance()`.

###

The Service Manager

All globally created system services are available via the `nsIServiceManager`, including the `nsIRepository` and `nsIRegistry`. Although the `nsIServiceManager` uses the Registry and Repository in the creation and maintenance of other services, the circular dependency is broken by not letting the `nsIServiceManager` create the `nsIRepository` and `nsIRegistry` instance and registering them specially with the `nsIServiceManager`. The nsIServiceManager is passed into NSGetFactory() for assisting the DLL in the Factory creation process.

###

Component Registration

Either at installation time of the Component or at times when the XPCOM library autodetect new/changed dlls, component registration is activated. The autodetection happens at startup time of the navigator or via a javascript trigger `navigator.repository.autodetect()`. The steps in component registration would be:

1. The dll is loaded
2. The component is allowed to self register by a call to a globally exported dll function `NSRegisterSelf()`. The `nsIServiceManager` and the fullpath of the dll are passed in to assist in the registration process. The dll is expected to create/modify its entries in the Registry according to the guidelines of the XPCOM hierarchy in the registry. `nsIRepository`, which can be queried from the `nsIServiceManager`, has useful registration functions that would easen the process.
3. The dll is unloaded

###

Autodetection of Components

Autodetection of changed dlls happened by storing the dll's last modified time and its size in the Registry automatically. If either the last modified time stamp or the filesize differs from that stored in the Registry for the dll, re-registration takes place. Before re-registration, the existing instances of the objects created by the classes in the dll are not freed because the `nsIRepository` has no list of them. The `NSCanUnload()` will be called with input parameter force set to `true`. The dll has to prepare for getting unloaded. After this call returns, the dll will be unloaded if the return value is `true`. If the dll detects that it cannot properly prepare for unloading, then it can return `false`. XPCOM will not let the re-registration of the modified dll proceed in this case. There is nothing much that XPCOM library can do to salvage this situation other than warning the user of possible instability and advice a restart upon which the re-registration will happen.

###

ProgID Spec

The general format of ProgIDs is component://netscape/compname?var=value;var=value;var=value...

Let us consider some more examples:

1. A pluggable protocol that implementes the nfs protocol
2. A converter that can handle application/x-zip
3. A plugin that can handle image/gif
4. A widget that can do a toolbar
5. A datasource that can handle mail
6. A helperapp that deals with application/postscript

All the above have what type they are and one or more arguments on what they particularly do.

The ProgID for these would look like

1. `component://netscape/network-protocol?type=nfs`
2. `component://netscape/data-converter?type=application/x-zip`
3. `component://netscape/plugin?type=image/gif;name=ImageMedia%20Gif%20Image%20Plugin;Description=Renders%20GIF%20Images....`
4. `component://netscape/widget?type=toolbar`
5. `component://netscape/rdf/datsource?type=mail`
6. `component://netscape/helperapp?type=application/postscript`

{Assume proper escaping of all above URI}

The above semantics would let ProgID be an extensible mechanism that could be searched on multiple ways. And
query on a progid should match only whatever was passed in. So a query for
component://netscape/plugin?type=image/gif should pass for the progid specified above. We could extend this
mechanism with wildcards, but I dont want to go there yet... :-)

###

Components created on events

NOTE: THIS IS NOT BEING DONE. We are going to expect the apps to this themselves by using the registry.

Some dlls have components that want to be created on certain events namely Startup, Shutdown (for now). Example is xpinstall.

> RegisterComponentForEvent(..., RegisterationTime when, ...)
> RegisterFactoryForEvent(..., RegistrationTime when,...)

exists for this purpose. When an application wants to Fire the particular event, it calls

> nsComponentManager::FireEvent(RegistrationTime when)

ComponentManager will look for components that are registered to be created on these events and do the following for each of the components:

1. CreateInstance(...,CID, knsIStartupComponentIID, &obj);
For a shutdown event, knsIShutdownComponentIID would be used.
2. obj->Release();
The component needs to take adequate measures to keep itself alive and figure out how it would delete the object, since a Release() happens immediately after a CreateInstace()

Warning: Order of creation of multiple components registered on the same event is not defined. Component dependencies aren't thought of yet.

###

How will all this help me

For Component Developers:

- Component dlls developed could be dropped into a directory, a JS function called after which your component is in business without even a restart of the browser. Of course, there needs to be someone accessing it.
- No need to export you CLSID or run around finding where to advertise your CLSID

For Component Users:

> - No more hacking in calls to `nsIRepository::RegisterFactory()`
> - No need to know the CLSID of components that you want to instantiate. Component creation can happen like this
> `nsIRepository::CreateInstance("component://netscape/network-protocol&type=nfs", NULL, kProtocolIID, &result);`
> instead of
> nsIRepository::CreateInstance(NFS_PROTOCOL_CID, NULL, domIID, &result);

###

What has happened so far

- Autoregistration implemented to grovel for components from the current directory
- nsRepository is a global object. Not nsIRepository exists.
- nsRepository::ProgIDToCLSID() implemented

###

Changes to XPCOM happening

- API changes to get ProgID in entire registration process for factories and components
- ProgID cache to improve performance of ProgID queries
- Notion of Component dlls living in a Components directory and autoregistration will load/manage only these components. Other components that are linked in to the app need to call `nsRepository::RegisterFactory()` from the app. To make this happen we need a list of these.
- nsRegistry: Enabling the new nsRegistry developed by Bill Law <law@netscape.com>
- Service Manager and Repository: Merge them
- Convenience function to createIntance(progid)
- Base ProgID in the interface ID header file

###

What should I do

Component Developers

- Implement `NSRegisterSelf()` for your component dlls. Sample implementation that perf http://lxr.mozilla.org/mozilla/source/modules/libpref/src/nsPref.cpp#608
- Use `nsRepository::RegisterComponent()` instead of `nsRepository::RegisterFactory()` if your component lives in a DLL
- Dont use static constructors in loadable components

Component Users

- Create all your components using ProgID rather than CID
- If you were thinking of managing creation of components yourself, think again. The repository may be already doing that.

###

Issues

- Need support for questions like:

1. Enumerate all CLSIDs that implement a particular interface.
2. Let a particular CLSID be the preferable implementation for an interface.
I dont know how this a XPCOM component user could use it unless there could be a call like:
`nsIRepository::CreateInstance(NULL, NULL, nsWidgetIID, &result);`
3. Enumerate all interfaces supported by a CLSID

- Resolving naming conflicts between ProgID
- Store component specific name-values under `ROOTKEY_COMMON\\component.class.version\\`
- Add a registry entry under `ROOTKEY_COMMON\\component.class.version\\`to indicate the willingness for a CLSID to behave as a Service.
- Add quick registration support functions in `nsIRepository` for components to use.
- Is the hierarchy `ROOTKEY_COMMON\\Classes\\CLSID` acceptable.
- Should we have a nsIRepository. JS needs it so that they can reflect it in javascript. Remove &'s in the API.

Last Modified: 28 Jan 1998
Feedback to: netscape.public.mozilla.xpcom