We recently reimplemented and fully revamped the the Moniker support in Bonobo. This work has opened a wide range of possibilities: from unifying the object naming space, to provide better integration in the system. Note: on this document I have ommited exception environments handling for the sake of explaining the technology.
2. Monikers - a user perspective
Monikers are used to name objects, they effectively implement an object naming space. You can obtain monikers either because you created the moniker manually, or from a stringified representation of a moniker.
Here is a list of stringified monikers, and an interpretation of it:
Now, what you saw above are some examples of stringified representations of monikers. This means that they are not really monikers, it is the way a Moniker is represented in string form.
Monikers typically are created either by using a Bonobo API call that transforms the stringified representation into an object (which exports the IDL:Bonobo/Moniker:1.0 interface), like this:
Now, a moniker is only interesting because it can yield other objects when resolved. During the resolution process, you specify which interface you are intersted on the moniker to return. This is achieved by invoking the ::resolve method on the moniker and passing the repoid of the interface you desire, like this:
This would request the moniker to return an object that implements the IDL:Bonobo/Control:1.0 interface. This means that the object could be embedded as a regular Bonobo control in applications.
Maybe you do not want to get a control, but rather to resolve the moniker against a different interface, for instance a Bonobo::PropertyBag interface:
The resolution process might yield completely different objects.
The parsing and resolution process is all encapsulated into a single API call for your convenience: the bonobo_get_object function:
The "x" object might launch Mozilla which would in turn load www.gnome.org, and the returned object can be used as a Bonobo Control, and used in your application as a widget.
The "y" object on the other hand does not need all the power of Mozilla, we are only requesting the very simple Stream interface, so we might be able to implement this with a lightweight HTTP implementation: maybe a wget-based bonobo server, or a libghttp server.
Note that even if the stringified versions of the monikers were the same (ie, "http://www.gnome.org") the resulting objects might differ wildely depending on the interface being requested.
3. The Moniker parsing system
During parsing the Moniker stringified, Bonobo will use the colon-terminated prefix as the toplevel moniker to be invoked for the resolution process.
For the prefix "file:" the file moniker will be used; For the prefix "oafid", the oafid moniker will be used; For the "queue:" prefix, the queue moniker will be used.
Once the moniker that handles a specific prefix has been activated, the moniker will be requested to parse the remaining of the string specification and return a valid moniker.
Each moniker typically will consume a number of bytes up to the point where its domain stops, will figure out what is the next moniker afterwards. Then it will activate the next moniker and pass the remaining of the moniker stringified version until the parsing is finished.
Each moniker is free to define its own mechanism for parsing, its special characters that are used to indicate the end of a moniker space, and the beginning of a new one (like the "#" and the "!" characters in some of the examples above). This flexibility is possible because each moniker gets to define its rules (and this is important, as we want to integrate with standards like http and file).
4. Monikers as an object naming scheme
As you can see, monikers are used to implement a naming system that can be used to reference and manipulate objects. As you might have noticed, the ::resolve method on the Moniker interface returns a Bonobo::Unknown interface. And by definition, the bonobo_get_object also returns a Bonobo::Unknown.
This means that the resulting object from the moniker resolution will always support ref, unref and query_interface methods. The Moniker object naming scheme is:
5. Creating Monikers
Monikers are created typically by API calls into the Bonobo runtime or by your own classes that implement monikers.
6. Object Name Space
Lets start simple. A moniker is a reference to an object. To actually use the object, you have to "resolve" the moniker. The term used in the literature is "binding the object".
The result of resolving the moniker is a Bonobo::Unknown object.
Think of a moniker as a pathname. And think of the binding process as the "open" system call on Unix.
In the case of the file system, the kernel does the "resolution" of each path element by parsing one element of the file system, and the Virtual File System switch uses the current file system + mount points to resolve the ultimate file name.
7. File Linking
Monikers were originally implemented as part of the Microsoft OLE2 compound document system. They can be used effectively by applications during drag and drop and cut and paste operations to pass objects that must be linked by other applications.
The source application would create a moniker for a given object that would fully identify it, and pass it trough a drag and drop operation or a cut and paste operation to the recipient application. The recipient application then can resolve the moniker against the interface required (in the Bonobo case, Bonobo/Embeddable, or Bonobo/Control would be a common choice).
Applications do not need to store the entire contents of linked information, they can just store a stringified representation of the moniker, and resolve it again at load time.
8. Instance initialization
Monikers can be used to initialize objects, as a way of passing arguments to your object. This is coupled with the Bonobo/ItemContainer interface and the Item Moniker.
The Item Moniker is covered later.
9. Resolution of a moniker against an interface
A moniker can be resolved against different interfaces. The resulting object might be different depending on the interface that is being resolved. To illustrate this, here is an example, lets say we have the "http://www.helixcode.com" string representation of a moniker.
The string representation of the moniker can be resolved against the "Bonobo/Control" interface:
This could return an embeddable Mozilla component that is suitable to be embedded into your application as a widget (because we are requesting the moniker to return a Bonobo/Control interface). If the interface is resolved against the "Bonobo/Stream" interface,maybe Mozilla is not required, and the process could use a smaller process that just provides Bonobo/Streams, say a corbaified wget.
The logic for this lives on the http: moniker handler.
10. Core monikers
Bonobo ships with a number of moniker handlers: the file moniker, the item moniker, the oafid moniker and the new moniker.
10.1. The file moniker
The file moniker is used to reference files. For instance:
The file moniker will scan its argument until it reaches the special characters `#' or `!' which indicate the end of the filename.
The file moniker will use the mime type associated with the file to find a component that will handle the file. Once the object handler has been invoked, the Moniker will try to feed the file to the component first through quering the PersistFile interface, and if this is not supported, through the PersistStream interface.
10.2. The item moniker
The item moniker is typically triggered by the "!" string in the middle. The item moniker can be used to implement custom object naming, or argument handling.
The item moniker parses the text following '!' until the next '!' character, this is called the argument of the item moniker. During the resolution process, the item moniker will request from its parent the Bonobo/ItemContainer interface and will invoke the getObject on this interface with the argument.
For example, in a Gnumeric spreadsheet this allows programmers to reference sub-objects by name. For instance, Workbooks can locate Sheet objects; Sheets can locate range names, cell names, or cell references.
This moniker would reference the sheet named `Sales' in the workbook contained in the sales.gnumeric spreadsheet:
This other would reference the cell that has been named `Total' inside the Sheet "Sales":
The way this works from the container perspective, is that the container will implement the getObject (string) method, and would respond to the getObject request.
Item monikers can also be used to perform instance initialization. The component that wants to support instance initialization needs to support the Bonobo/ItemContainer interface and implement a getObject method that would return the object properly initialized.
For example, lets consider an image viewer component that can
be configured, like this:
The above example would activate the EOG component because of the file.jpg match, and then invoke EOG's ItemContainer implementation with the argument "convert_to_gray=on". getObject should return an object (which would be itself) but it would modify the instance data to set the "convert_to_gray" flag to on. Like this:
10.3. The oafiid moniker
The oafid: moniker handles activation using the Object Activation Framework. This allows application programmers to activate objects by their OAF ID, like this:
10.4. The "new:" moniker
The new moniker requests from its parent the "Bonobo/GenericFactory" interface and invokes the method create_instance in the interface.
Typically this moniker would be invoked like this:
In the example above "RandomFactory" is the OAFID for the factory for a certain object. During the resolution process, the "new:" moniker would request its parent to resolve against the IDL:GNOME/ObjectFactory:1.0 interface (which is the traditional factory interface in GNOME for creating new object instances) and then invoke the new_instance method on it.
Historically GNORBA (the old GNOME object activation system) and OAF (the new object activation system) implemented a special "hack" to do this same processing. Basically, the description files for the object activation system was overloaded, there were three types of activation mechanism defined:
The "new:" moniker basically obviates the need for the last step in the activation system. With OAF, using the OAF approach proves to be more useful, as it is possible to query OAF for components that have certain attributes, and the attributes for a factory object are not as interesting as the attributes for the instances themselves. Despite this, the "new:" moniker can be used for performing the operation of instance initialization in more complex scenarios that go beyond the scope of activation provided by OAF.
11. Adding moniker handlers to the system
12. Ideal monikers: There are two moniker handlers that would be interesting to implement: the Configuration Moniker and the VFS moniker.
They both help the system overall, because the added simplicity of having a standard way of activating services in the system and given that the API to these services is CORBA-based, any programming language with CORBA/Bonobo support can make use of them without the need of a special language binding.
I am convinced that this helps make the system more self consistant internally.
12.1. The Configuration Moniker
The configuration moniker is invoked by using the "config:" prefix. The string afterwards is the configuration locator. The moniker should support being querried against the "Bonobo/Property" or "Bonobo/PropertyBag" depending on whether we are requesting a set of attributes, or a single attribute.
For example, retrieving the configuration information for a specific configuration property in Gnumeric would work like this:
The Bonobo::Property interface is pretty comprehensive, and should address most needs, the methods are:
Now, this interface as you can see does not specify an implementation for the actual backend. Given that this is just an interface, we do not care what the moniker will connect us to, we only care with the fact that we will be able to use the Property and PropertyBag interfaces.
12.1.1. Configuration transactions.
Handling of transactional changes to the configuration system can be achieved by the use of the setValues interface in the PropertyBag. The implementation of the PropertyBag can either accept the values set, or it can do consistency checking of the values being set (for instance, to avoid the configuration to contradict itself, or store invalid values). If the values being set are invalid, an exception is thrown.
It would be also possible to hook up an arbitrary consistency checking component in the middle, by inserting the consistency checking in the middle of the stream, like this:
Notice the `gnumeric-consistency-check:' moniker handler. This could just be a shared library consistency checking component if it needs to be.
12.1.2. Listening to changes.
One of the requirements for a modern desktop is to be react globally when changes are made to global settings. For example, in the GNOME desktop when a theme is changed, a special protocol inside Gtk+ is used to notify all the applications that they should reload their theme configuration.
There are many other examples where applications need to keep track of the current setting. For example, when a preference is changed, we want the preference to take place right away, without us having to restart our running applications.
This is easily achieved by registering a Listener with the Bonobo/EventSource in the PropertyBag.
12.1.3. What about GConf?
GConf is a configuration management infrastructure that provides the following features:
There are two drawbacks to GConf currently:
The actual engine and backend for GConf could become the configuration moniker handler, only the API would be replaced as well as the actual storage system to support the more complete CORBA_Any, and the ad-hoc CORBA interface can be replaced with a more powerful system.
12.1.4. Configuration management: Open Issues
126.96.36.199. Specifying the location for configuration.
The syntax for accessing the configuration has not been defined, but we can cook this up pretty easily.
Forcing the configuration data to be loaded from a specific location. Although the arguments to the moniker could be used to encode a specific location, for example:
It seems more natural to use the file moniker to provide this information, for example:
The config moniker can test for the presence of a parent, and if the parent exists, then it would request one of the Persist interfaces from it to load the actual configuration file, and provide access to it.
188.8.131.52. Transactional setting of values.
It might make sense to "batch" a number of changes done under a prefix to avoid listeners to a range of keys to reset themselves multiple times. Consider the case in which a command line tool makes various changes to the background properties, say the changes are done in this order:
If the real configuration program for handling the background is running at that point, it will have registered to be notified of changes to all those values. The changes might be very expensive. For example the code migh react to every change and recompute the whole background image on each change.
An optimization would be to tag the beginning of the transaction and the end of it in the client code to allow listeners to get batched notification of changes:
This would allow the listener code to batch all the expensive requests into a single pass.
184.108.40.206. Configuration handlers
Consider the example above, we would like to be able to change properties on the system and have those properties to take effect independently of whether a listener is registered or not.
A property handler might register with the configuration moniker to be launched when a property changes. This could be done in a file installed in a special location.
12.2. The GNOME VFS becomes deprecated.
The GNOME VFS provides an asyncronouse file-system interface abstraction that can be used to access local files, remote files, files in compressed files and more.
The problem with the GNOME VFS is that it is very limited: it can only expose a file system like interface to its clients (very much like the Unix interface after which it was modeled).
As covered before in the `Object Naming Space', Monikers define an object naming space, and monikers can be defined for any type of resource that the GNOME VFS supports (a transitional path might include a set of monikers implemented on top of the actual GNOME VFS).
A file dialog could request a moniker to be resolved against a "Graphical File Listing" interface, which might result in a miniature Nautilus window to be embedded in the dialog box.
It would be possible to entirely reuse the existing GNOME VFS code by providing monikers for the various access methods that would handle the special cases "Stream", "Storage" and "FileListing". Other interfaces will be plugged into the moniker handler to support the richer content.
For instance, consider the "trashcan:" moniker. The trashcan moniker could be resolved against various interfaces. A file manager would resolve it against a DirectoryListing interface to display the contents of it; It could resolve it against a "Control" interface to get a trahscan custom view (to configure the values in the trashcan); a PropertyBag interface could be used to programmatically configure the various settings in it.
13. Other monikers
There is another family of moniker handlers that are worth stuyding. The filtering moniker handlers and the caching moniker handlers.
13.1. The streamcache: moniker
The idea of the streamcache: moniker is to be basically a shared library moniker handler that provides a cache for the IDL:Bonobo/Stream:1.0 interface. This moniker is very simple, during resolution it requests the IDL:Bonobo/Stream:1.0 interface from its parent and it can only expose the IDL:Bonobo/Stream:1.0 interface to clients.
The plus is this: it is a shared library component, which will run in the address space of the application that will use the Stream, and it provides a cache to the parent Stream (so we can use small granular method invocations, and the stream cache can do the traditional buffering).
Think of this difference as the one between an application using write()/read and the application using fwrite/fread/getc/putc: although many applications can implement their own buffering, most of the time just using the libc-provided ones (fwrite/fread/getc/putc) will do it. This is exactly what the streamcache: moniker will do: By appending this to a stringified representation of a moniker, you can get a Stream cache for free.
13.2. The #gunzip, #utar filtering monikers
The #utar moniker is a moniker that would implement tar file decoding (the same concept can be used for other archive formats). This moniker uses an auxiliary tar component handler. The moniker connects the tar component handler to the parent object's Stream interface and returns the resulting object. The result of the #utar moniker can be either a Bonobo/Stream (for a file reference) or Bonobo/Storage (for a directory reference).
The beauty of this system is that if two applications use the same moniker, they would be sharing the same data without having to uncompress two times the same tar file.
This is all achieved transparently. This would happen in quite a few instances, for example, if you are exploring a compressed tar file in a file manager and you drag the file to another Moniker-aware application, say Gnumeric, Gnumeric would be using the same file that was openened by the file manager instead of having two uncompressed sets of files in your system.
The above scenario is particularly useful if you have little space, or if the process of untaring a file would take a long time.
13.3. The propertycache: moniker
Accessing individual properties over and over might take quite some time due to the CORBA round trips. The propertycache: moniker would be also a shared library handler that would basically activate the property moniker, and would set up property listeners (which would be notified of changes in the property data base).
So if your application does a lot of queries to a property, you might just want to append this to improve performance and not care about doing clustered reads, the cache would do this for you.
This is not implemented, as it requires the property moniker to be written.
14. The accidental invention.
Monikers were invented originally in OLE2 to implement Object Linking. The OLE2 programmers accidentally invented an object naming system.
This object naming system is not only very powerful, but it is extensible and it helps make the system more consistent.
15. Monikers and the GNOME VFS.
Some people ask: monikers look as if they are just re-implementing the GNOME-VFS, why is that?
For a storage backend you can always use something like bonobo_storage_new ("gnome-vfs") and get away with life. The main difference between the gnome-vfs, and monikers is that monikers are used to implement an object-based name space, while the gnome-vfs is a fine abstraction for naming files and directories. The moniker space goes well beyond this. When Ettore, Nat and I designed the GNOME VFS in Paris Ettore had a grander vision than Nat and I had. Nat and I wanted exactly what the GNOME VFS is: an asyncronous, pluggable virtual file system implementation. Ettore wanted something more general, something that would implement an object name space. And some of the design decisions in the core of the gnome-vfs reflect some of this thinking, but the API and the infrastructure was limited to handling files. Various months later, we finally understood completely the moniker system, and we realized that monikers were an object naming space, and that if done correctly monikers would be able to implement Ettore's initial vision for having an object-based naming space.
16. Open Issues
We will need to research the implementation requirements for asyncronous parsing and resolution of Monikers.
Currently, both the Object Activation Framework and Bonobo support asyncronous activation. Implementing this for Monikers should not be hard, but might require a few changes in the Moniker interface.
Monikers are very powerful mechanisms that can unify the name space of objects in the system and can be used to provide a uniform access method for a wide variety of tasks:
The Bonobo moniker implementation was done by Michael Meeks.
The design for the Bonobo moniker system was done by Ettore Perazzoli, Michael Meeks and myself.