Platform SDK: Active Directory, ADSI, and Directory Services

Implementation Issues for ADSI Providers

In addition to issues listed here, details for implementations can be found in the ADSI Reference under Notes to Implementers and When to Implement.

The recommended way to implement the ADSI interfaces is to first implement the pure COM interface IDirectoryObject. By providing this "minimal-overhead" layer, you supply client applications with control most needed for performance reasons and supply your own implementation with the most flexibility.

Next implement the fundamental ADSI interfaces, IADs, IADsContainer, IADsCollection, and the IADsPropertyValue, IADsPropertyEntry, IADsPropertyList property cache interfaces. IADsGroup and IADsMembers are also interfaces in frequent demand by system administration software.

Implement the schema management interfaces if your directory service has an underlying schema: IADsClass, IADsProperty, IADsSyntax. If there is no underlying schema, you can use these interfaces to abstract the classes and properties used by the directory service as if there were. Schemas are a great means of publishing the features of your directory service to ADSI clients.

Next implement the meta-interfaces so that your directory service features are accessible to ADSI clients that use a single user interface.

Collections

ADSI provider components can follow one of three models for caching collections during enumeration. The choice of a caching model determines the behavior of ADSI when an object in a collection is deleted from the underlying directory service "outside" of ADSI.

The caching models are:

  1. Collections are cached in advance. The collection of object instances is retrieved from the underlying directory service in its entirety when IADsCollection::get__NewEnum is called to create a new enumerator object. If the source object for an Active Directory object instance in the retrieved collection is deleted from the underlying directory service the client does not become aware of the deletion until a IADs::GetInfo or IADs::SetInfo attempts to access the collection.
  2. Collections are incrementally cached. The collection is retrieved from the underlying directory service one object at a time when IEnumVARIANT::Next is called. IEnumVARIANT::Reset will return to the beginning of the collection in the cache and IEnumVARIANT::Next will return cached objects until the end of the cache is reached, at which point new objects will be added from the underlying store. Once an Active Directory object instance is in the cache the client will not become aware of its deletion from the underlying directory service until an IADs::GetInfo or IADs::SetInfo attempts to access the object.
  3. Collections are not cached. The collection is retrieved from the underlying directory service one object at a time when IEnumVARIANT::Next is called. IEnumVARIANT::Reset will return to the beginning of the collection in the underlying store. IEnumVARIANT::Next and IEnumVARIANT::Reset operations cannot retrieve deleted objects, since the objects are fetched on demand from the underlying directory service. Only the "current" object is cached; if the current object is deleted, the client will not become aware of its deletion from the underlying directory service until a IADs::GetInfo or IADs::SetInfo attempts to access the object.

Regardless of the caching model, it is important to note that ADSI enumeration returns Active Directory service interfaces to the caller. To avoid the overhead of obtaining a new interface pointer, ADSI applications should cache the returned interface pointers for object(s) they intend to manipulate. For example, a Visual Basic program that enumerates a container and populates a listbox with names can cache the interface pointers associated with the names for later use. This approach will provide greater performance than populating the listbox during enumeration and obtaining a new interface pointer when the user makes a selection.

About Dispatch IDs

IDispatch is an Automation interface defined by COM for controllers that do not use COM interfaces directly. Accessing an object through IDispatch is called name-bound or late-bound access, since it occurs at run time ("late") and uses string names of properties and methods to resolve references ("name"). At run time, clients pass the string name of the property or method they wish to call into the IDispatch::GetIDsOfNames() method. If the property or method exists on the object, the dispatch identifier (dispID) of the corresponding function is retrieved. The dispID is then used to execute the function through IDispatch::Invoke(). Using IDispatch, properties and methods on the interfaces exposed by a single object appear as a flat list. Because name-bound access requires two function calls, it is less efficient than using a COM interface directly. Clients are encouraged to use the ADSI COM interfaces on the objects when performance is a consideration. Advanced Automation controllers such as Visual Basic 4.0 can call other COM interfaces as well as IDispatch, if the interfaces comply with the Automation constraints for data types and parameter passing.

ADSI providers generate dispIDs dynamically for each Active Directory object. The dispIDs retrieved through IDispatch::GetIDsOfNames for a given object are the generated values, but not the values that are in the IDL for the object. IDispatch users must call GetIDsOfNames to obtain valid dispIDs at run time.

Type Information and Type Libraries

The ADSI SDK supplies a type library, ActiveDs.tlb, that documents all the standard interfaces supported by ADSI. A provider must supply a similar type library for all interfaces found in ActiveDs.TLB, plus any additional type information for the interfaces that are implemented within the provider component. Additional information about type libraries can be found in Generating a Type Library With MIDL under Tools Guide in the Microsoft Platform SDK.

An example of IDL code:

[ object, uuid(IID_IADsXYZ), oleautomation, dual ]
interface IADsXYZ: IDispatch
{
// Read-only properties.
[propget]
HRESULT AReadOnlyProp ([out, retval]BSTR *pbstrAReadOnlyProp);
 
// Read/write properties.
[propget]
HRESULT AReadWriteProp ([out, retval]long *plAReadWriteProp);
[propput]
HRESULT AReadWriteProp ([in]long lAReadWriteProp);
 
// Methods.
HRESULT AMethod ([in]DATE dateInParameter,
[out, retval]BSTR *pbstrReturnValue);
}; 

Thread Safety

Windows NT/Windows 2000 and Windows 95 support multiple threads in a single process. ADSI providers may therefore be used in multithreaded applications and must allow for this.

The Component Object Model describes the following three different threading models. COM programs indicate which model is in use when initializing the COM library using the CoInitialize and CoInitializeEx functions.

ADSI does not assume any particular threading model. Writers of provider components should assume the free threading model and guarantee the consistency of their internal data structures by protecting them from thread-unsafe (that is, uncoordinated) updates through the use of synchronization objects such as critical sections or semaphores.

Object Locking

ADSI does not impose or define an object-locking scheme. Providers for namespaces that support access serialization using locking can expose the underlying locking scheme through provider-specific extensions to ADSI.

Property Names Within a Schema

ADSI represents properties as property objects within the ADSI schema container. This requires that property names be unique within each schema container. It is up to the provider to ensure there are no name collisions.

Primary Interface

When a provider does not know what interface should be returned as the primary interface, IID_IADs should be returned. This provides name-bound access to all properties of an object through IDispatch and the IADs::Get, IADs::GetEx, IADs::Put, and IADs::PutEx methods.