Namespaces

The MMLite Base provides a namespace facility to locate, activate, and manipulate objects. The namespace registers each object and assigns it a name. The names are null-terminated ASCII strings. Once registered, an object is available to any other thread that knows its name, via the Bind method.

The namespace provides a means to associate an object with a name. Internally, a namespace object is implemented as a look-up table in which each entry consists of an object pointer paired with a name. An object must support the IUnknown interface.

The Base automatically creates the system namespace. A program calls the CurrentNameSpace function to obtain a pointer to the system namespace. Additional namespaces are created by calling the INameSpace::Bind method with the Flags parameter set to enable the NAME_SPACE_CREATE option.

A namespace object can be registered as an object in another namespace object. In this manner, namespace trees of arbitrary height and complexity can be constructed.

The INameSpace interface provides the following methods for manipulating the namespace.

CurrentNameSpace

 

Returns a pointer to the interface object for the system namespace.

INameSpace::Bind

 

Translates a name into an object reference.

INameSpace::FindNext

 

Finds the next object in the namespace or a subnamespace.

INameSpace::GetCapabilities

 

Gets object capabilities.

INameSpace::Register

 

Adds a name and the associated object to the namespace.

INameSpace::Unregister

 

Removes a registered name and object from the namespace.

NAME_SPACE_FLAGS

 

Option flags for namespace interface.

 

Namespaces

A namespace is a simple database or registry that associates names with objects. A namespace is itself an object, a namespace object that exposes the system INameSpace interface to programs. This interface provides a way for unrelated threads to offer and receive special services without those services having to be incorporated into the base itself. Like the other parts of MMLite, the namespace implementation is small and efficient.

The following sections contain a general discussion of namespaces. For detailed information about methods for manipulating namespaces, see the description for the INameSpace interface in the Reference.

Introduction

Inside each namespace object is a look-up table. Each entry in the table is a name paired with an object pointer. When instructing a namespace to create a new entry, a program specifies both a pointer to an object and to the object's name. The {name, object} pair is stored as a table entry within the namespace so that a program can later retrieve the object pointer from the namespace by specifying the object's name.

A namespace is similar to a file directory system. The program that creates a file assigns a name to the file. If a second program knows the name of the file, it can obtain access to the file created by the first program. The INameSpace interface operates in much the same way but is somewhat more general than a file system because it allows the assignment of names to objects other than files.

One way to make a service available to multiple client programs (or threads) is to embody a group of related methods in an object and then register the object in a namespace. A server thread offers a service by calling the INameSpace::Register method to specify that a well known name and an associated object pointer are to be registered together within a namespace. The term "well known" means that the name is known to the clients that need to use the object.

Following boot-up, the clients may know the object by its name only, and they have no direct way of determining where in memory the object is located. Once a {name, object} pair has been registered in the namespace, however, a client can call the INameSpace::Bind method to obtain a pointer to the object from the namespace.

The client subsequently uses the pointer rather than the name to refer to the object because doing so is more efficient. The namespace lookup is typically used only to configure the system by initializing the links between the clients and the objects they use. Thereafter, the clients access the objects directly without intervention by the namespace.

Conventions for Objects and Names

An object registered in a namespace must conform to Microsoft's common object model (COM) standard. A COM object exposes one or more well-defined software interfaces. Strictly speaking, what the namespace stores is a pointer to an interface, which is implemented by an object, rather than a pointer to the object itself. By obtaining this pointer, a program gains access to the functionality and information encapsulated within the object. The following conventions apply to objects registered in a namespace:

    A name is a null-terminated string of characters.

    The conventions for legal names are application dependent. You might, for instance, choose to assign the name "MAU Memory Manager" to an object that allocates memory in blocks of 128 bytes.

    Names are case sensitive. The strings "Fred", "fred", and "FRED" are recognized as different names.

    Spaces are significant. The strings "Joe Smith" and "JoeSmith" are treated as different names.

    Names can include special characters. Examples of legal names are "FOO$BAR", "Joe R. Smith", "wave_table", "fredj@msn.com", "/sockets/", and so on.

 

Namespace Hierarchies

Similar to early versions of MS-DOS, the current namespace implementation has a flat directory structure. In the future, the namespace implementation may be extended to directly handle subdirectories and compound pathnames, but these are not currently supported. NOT TRUE SUBDIRECTORY SUPPORT HAS BEEN ADDED. These features, however, are largely conveniences. In fact, most of the capabilities provided by subdirectories and compound pathnames are already available in one form or another in the current namespace implementation.

In particular, because a namespace is itself an object, one namespace can be registered as an object in another namespace. With this technique, namespaces can be linked together to form namespace trees of arbitrary height and complexity.

The lack of direct support for subdirectories, however, means that the application program must traverse a path specified by a compound pathname (for example, "xxx/yyy/zzz") by breaking the pathname into its constituent names ("xxx/", "yyy/", and "zzz"). The application then traverses the path one level at a time, with each level requiring a separate call to the INameSpace::Bind method.

System Namespace

The system provides a default namespace, called the system namespace, which is used and maintained by the system as a central registry for named objects. The system namespace serves as the root directory for all sub-namespaces that are subsequently attached to it. In order to make an object accessible to clients, the server for a particular object registers the object with the system namespace. A client searching for an object typically begins with the system namespace and descends through the namespace hierarchy to the subnamespace that contains the object reference.

All that is required to begin the traversal is a pointer to the system namespace object. A call to the CurrentNameSpace function serves this purpose. It returns a pointer to the system namespace.

The system namespace exists permanently. It cannot be deleted. The base boot code creates the system namespace immediately following boot-up.

Creating New Namespaces

Only the system namespace is created automatically. All additional namespaces must be created by servers or application programs.

A server may provide a namespace containing either a single kind of object or a family of similar objects. A server's namespace may include a separate sub-tree for each kind of object. A server may choose to register its namespace as an object in the system namespace.

New namespace objects are created by calling the INameSpace::Bind method with a Flags parameter that specifies the NAME_SPACE_CREATE option. This same call registers the new namespace as an object in a previously existing namespace. If required, the new namespace can be detached from the original namespace by calling the INameSpace::Unregister method.

In the current implementation of the base INameSpace implementation, a namespace object is incapable of creating any object other than another namespace object.

Object Reference Counts

The INameSpace interface maintains accurate reference counts on the objects it registers.

Every COM object implements the AddRef and Release methods to increment and decrement, respectively, its reference count. When a new copy of a pointer to a COM object is created, the object increments its internal reference count by one. When the pointer is discarded, the object decrements the count. When the last pointer is released and the count goes to zero, the object deletes itself and frees the storage allocated to hold it.

At the time that an object is registered with a namespace, the namespace stores a pointer to the object and calls the object's AddRef method to increment its reference count. Even if all threads in the system subsequently release the object, the object persists as long as it remains registered in the namespace. When a program calls the INameSpace::Unregister method to remove an object from the namespace, the namespace calls the object's Release method to decrement the object's reference count accordingly.

When a namespace responds to an INameSpace::Bind call by outputting an object pointer, the namespace increments the object's reference count on behalf of the calling thread. This means that the thread itself does not have to call AddRef, because the object's reference count will already have been incremented by the time the INameSpace::Bind call has completed.

This mechanism prevents the potential timing hazard that can arise if the calling thread is required to call AddRef itself to increment the object's reference count. Specifically, the thread might be preempted between the return from INameSpace::Bind and the call to AddRef. During the preemption, the object might be unregistered from the namespace, decrementing the object's reference count to zero and freeing the storage allocated for the object. A fatal error would occur later when the thread resumes execution and tries to perform an AddRef on the deallocated object. Performing the AddRef inside INameSpace::Bind, where the appropriate synchronization can be ensured, eliminates this possibility.

Interface Pointers

To ensure portability of application code, the you must correctly handle the object pointers that are entered into and retrieved from a namespace. In particular, you must understand the conditions under which you can safely type-cast from one type of object pointer to another.

For example, assume that an application program defines a proprietary interface, IFoo, that is derived from IUnknown. Also assume that the application defines variables pFoo and pUnk to be pointers to interfaces of type IFoo and IUnknown, respectively. If pFoo points to an object's IFoo interface, the following assignment is perfectly safe because every IFoo interface is also an IUnknown interface.

pUnk = (IUnknown *)pFoo;    /* This is okay. */

 

On the other hand, the following assignment is erroneous.

pFoo = (IFoo *)pUnk;    /* Don't do this! */

 

Even if the object pointed to by pUnk exposes both IUnknown and IFoo interfaces, the pointers to these interfaces may not have the same value. A program that assumes that the two pointers are identical may or may not execute correctly depending on the local implementation of the IFoo object. This is poor programming practice and should be avoided.

In addition, the underlying implementation of the INameSpace interface may vary from one system to the next. Even if the program that calls INameSpace::Register originally specifies a pointer to an object's IFoo interface, the pointer output by the INameSpace::Bind method is only guaranteed to point to the object's IUnknown interface. An application program that assumes otherwise may not be portable.

The INameSpace::Register method registers an object in a namespace. The object pointer is specified in the first parameter, pObj, which is defined to be a pointer to an IUnknown interface. Assuming that the pFoo variable is defined to be a pointer to an IFoo interface and that pNmSpc is a valid namespace pointer, the following call is perfectly safe:

pNmSpc->Register((IUnknown *)pFoo, 0, NULL);    /* This is okay. */

 

Type-casting pFoo to an IUnknown interface pointer is perfectly safe because, as explained previously, the IFoo interface is derived from IUnknown.

The situation is different, however, when using the INameSpace::Bind method to retrieve an object pointer from a namespace. The INameSpace::Bind method copies the object pointer to the location pointed to by the method's last parameter, ppObject. The object pointer is defined to be a pointer to an interface of type IUnknown.

If what you really require is a pointer to the object's IFoo interface, you may be erroneously tempted to do something like this:

pNmSpc->Bind("foo", 0, (IUnknown **)&pFoo);    /* Don't do this! */

 

Type-casting pFoo as shown above is inaccurate. It forces INameSpace::Bind to copy the IUnknown pointer value into the IFoo pointer variable pFoo, which is risky at best. If the system supports multiple address spaces, the object might in fact reside in a different address space, and would only be accessible via RPC. In this case the code above will later lead to fatal errors, because there are no methods in the object's proxy other than the IUnknown's.

The preceding code example is erroneous in another way. Suppose the implementor of some object wishes to support multiple interfaces for the same (logical) object. Each one of these interfaces might, or might not, physically correspond to the IUnknown interface of the object. Moreover, someday the implementor might revise the implementation, changing what corresponds to the IUnknown interface registered in the namespace.

The correct way to obtain a pointer to an object's IFoo interface is to call the QueryInterface method on the IUnknown pointer obtained from the call to INameSpace::Bind. The following code fragment illustrates this.

extern IID IID_IFoo;

 

INameSpace *pNmSpc = CurrentNameSpace();

IUnknown *pUnk;

IFoo *pFoo;

SCODE sc;

 

/* Retrieve pointer to "foo" object's IUnknown interface. */

sc = pNmSpc->Bind("foo", 0, &pUnk);

if (sc != S_OK) {

    printf(_TEXT("Failed to bind to object \"foo\".\n"));

    exit(0);

}

...

/* Obtain pointer to object's IFoo interface. */

sc = pUnk->QueryInterface(&IID_IFoo, &pFoo);    /* correct way */

if (sc != NOERROR) {

    printf(_TEXT("IFoo interface not supported.\n"));

    exit(0);

}

...

/* Discard object reference through pointer pUnk. */

pUnk->Release();

...

 

The last statement in the preceding example releases the reference to the object's IUnknown interface through pointer pUnk. The reference to the object's IFoo interface through pointer pFoo still exists, however. Remember that when QueryInterface outputs the new pointer, it increments the object's reference count accordingly. If, after the QueryInterface call, you have no further use for the IUnknown interface pointer, remember to call Release on the pointer before you discard it.

After completing the preceding steps, the application program is ready to begin using pointer pFoo. When finished with pFoo, the application should release the object reference before discarding the pointer, as the following example shows.

/* Discard object reference through pointer pFoo. */

pFoo->Release();