Novell Home

The Anatomy of a Simple IntranetWare Client/Server Application: Part 2

Articles and Tips: article

LAWRENCE V. FISHER
Senior Research Engineer
Developer Information

01 Oct 1997


Second in a series of articles centered on the development of a real client/server application called API_Info. This article focuses on developing a snapin and covers such topics as NDS schema composition, designing new classes an attribues, and extending the NDS schema.

Introduction

Novell Developer Information, the source of this publication, recently developed a CBT CD-ROM entitled "Intranetware Programming Fundamentals". This article is the second in a series which will take selections from the CBT and condense them for presentation to you in this magazine.

The purpose of this series is to give new Novell programmers a wholistic view of Intranetware developer issues and tasks. To enable that to happen, the discussions in the articles will revolve around the development of a simple but real client/server application called API_Info.

Note: The first article discussed how to develop a simple NLM and use an NCP (NetWare Core Protocol) extension to expose the NLM's service on the network. The next and last article will discuss how to develop a simple IntranetWare client that will manage tree lists to select a tree, search the selected tree for an optimal service instance, and establish an authenticated connection from the client to the server running the optimal service instance. Introduction

This article will discuss the following topics as they relate to API_Info:

  • NDS schema composition

  • Designing new classes and attributes

  • Extending an NDS schema

  • The NWAdmin/snapin protocol

This article will explain how to extend the NDS schema to accommodate an application's special object types and how to develop a snapin to enable administrators to manage them.

Client 32, Novell's current multi-platform client software, allows a client to authenticate to multiple trees. Each tree maintains its own distributed NDS database.

NetWare administrators generally use a network administration application provided by Novell to view and help manage their trees. For Windows 95, this administration application is called NetWare Administrator.

NetWare programmers must view the network from two different views. The NDS tree view describes how the administrator has organized the entities on the network. The network layout view, on the other hand, is used to see how physical communication is accomplished between the network entities represented by the objects in the NDS tree.

How API_Info Uses NDS

In the NDS tree view, each network entity is called an object. Shown in Figure 1 is a simple tree with user objects, server objects, an organization object, and organizational unit objects. In addition, NDS can be extended to contain custom objects designed for special applications like API_Info.

The object type used depends on the purpose of the network entity as understood by the administrator. Each object contains the specific information needed for it to perform its role in the network.

There are many standard object types provided by Novell. However, often a client/server programmer may need to add a new object type for special use by an application.

An object can be thought of as a record in the NDS database. For example, user objects contain information about the persons who operate the client stations on the network. In NetWare Administrator, the objects can be double-clicked to display the object's information in what is called a multi-page edit window. The information fields in this window are called attributes.

Tree administrators arbitrarily create, edit, and delete objects on a tree as they determine that it is necessary. If you have never used NetWare Adminstrator to perform these tasks, now would be a good time to try them before continuing with this article.

An organization object like the Developer Org object in Figure 1 is the highest-level container in the tree. There can be multiple organization objects but the administrator for this tree has created only one.

Organizational units such as the Tech Info and Education objects in Figure 1 are the mid-level containers in the tree. Administrators nest organizational units within the tree to represent the structure within the organization.

Also in Figure 1, you can see that there are no entities in the network layout which correspond to the organization or organizational unit objects.

Figure 1: There can be many trees in a given network layout.

User objects in an NDS tree are created by an administrator to represent the users that belong on a tree. There is not a one-to-one correspondence between user objects on the tree and users in the network layout. In fact, one user can belong to many trees.

For example, if a new client station for Harriet were to be added to a network layout, a different user object for Harriet could then be added to and managed from several trees. Users can belong to as many trees as necessary, but they must have a separate user object on each tree they belong to. This means that users must have a separate password for each of their trees.

Notice that the tree in Figure 1 also has server objects that the administrator has created to represent the servers that belong on the tree. Unlike user objects, a server can only belong to one tree because it may be used to help manage the NDS tree's distributed data base.

Before we start with our discussion of how API_Info uses NDS to enable clients to obtain a connection to the most optimal instance of an API_Info service NLM, let's assume that the administrator has loaded the API_Info NLM on all the servers in the tree shown in Figure 1.

After loading the API_Info NLM on a tree's servers, an administrator would create API_Info objects for each separate instance of the running NLM. The purpose of these API_Info objects is to enable API_Info clients to find servers running their NLM. API_Info objects are not standard NDS objects.

In fact, much of the work that you will do in writing an NDS enabled application will be to create an NDS object template or class for the application's object type/s and develop a snapin module so that the administrator can create, edit, and delete them using the NetWare Administrator application. We will discuss these tasks more towards the end of this article.

API_Info Service Acquisition

Shown below are the steps API_Info takes in using NDS to obtain an optimal service connection on the selected tree.

  1. Build a list of trees and allow the user to select one. The API_Info NLM could be running on multiple servers on multiple trees. Some NetWare networks are vast, with hundreds of trees. In the next article, we will explain how to build lists of connected and unconnected trees and then allow the user to select one of them to be searched for the tree's optimal API_Info NLM instance. For this article, we will simply assume that a tree has been selected by the user and that we know the name of that tree.

  2. Set NDS context Step two is to set up an NDS context that accurately describes our user object's location on this tree (NDS name context refers to a location in the NDS Directory tree, while NLM context refers to the resources available to the threads of an NLM. These terms are not interchangeable.)

    In Figure 2, you can see that organizations are at the top of the tree and leaves are at the bottom. In between are multiple levels of Organizational Units that the administrators can use to organize their users. Altogether, the organizations, organizational units, and leaves compose an NDS Directory tree.

    NDS context is similar to a pathname in that it describes the location of an object in a tree. In the lower part of Figure 2, each of the NDS name contexts describes the path to Joe in Accounting.

    Figure 2: NDS Name context for Joe in accounting.

    The first two name contexts shown in Figure 2 are referred to as distinguished because, as complete paths, they can distinguish between the Joe in Accounting and the Joe in Marketing. If the types for each step along the way are specified, the context is also referred to as typeful. ( CN refers to a leaf object's "Common Name").

    A relative name context on the other hand, requires that the station's current context be set to the path information required to fully distinguish a target object. In the example in figure 2, current context would need to be set to "Accounts_Receivable.Accounting.Novell".

    When programming with NDS, there are two ways to set up an NDS name context, the old way and the new way.

    // Creating NDS User Name Context - The Old Way
    
    1   NWDSContextHandle context ;
    
    2   
    
    3   NWSetPreferredDSTree ( "DevServicesTree" );
    
    4   context = NWDSCreateContext ( ) ;
    
    5   NWDSSetContext ( context , DCK_NAME_CONTEXT , "name.context.string" ) ;
    
    6   NWDSSetContext ( context , DCK_FLAGS , &contextFlags ) ;&

    In the old way, we first set the preferred tree variable. This variable has global scope for the client. That is, all applications running on that client machine will be affected by this variable's setting. This side effect wasn't serious in old clients which could only be logged in to one tree at a time. But NetWare's current clients can log in to multiple trees.

    This means that by using the preferred tree variable in setting up the context for your connection, you are putting your application at risk because some other application might change its value behind your back.

    Conversely, if you change the value of the preferred tree variable, you may be doing harm to some other application which is depending on it to be in a previous state.

    The fields for the context created with the NWDSCreateContext( ) call are shown in Figure 3. Note that there are only eight fields in this context.

    Figure 3: Fields in a context created by NWDSCreateContext( ).


    Key Constant
    Data Type
    Explanation

    DCK_FLAGS

    uint32

    Flags that govern the way that NDS requests areprocessed.

    DCK_NAME_CONTEXT

    char*

    Defines the default naming path for the NDS request

    DCK_CONFIDENCE

    uint32

    Set by the caller to establish the level of risk vs.expense acceptable to receive reliable information from NDS.

    DCK_TRANSPORT_TYPE

    uint32

    Defines the underlying transport used for the NDS session.

    DCK_REFERRAL_SCOPE

    uint32 (2)

    Defines whether one server refers the client agent to a second server to satisfy an NDS request. (Not used by NLMs)

    DCK_LAST_CONNECTION

    Net_Address_T

    Stores the network address of the last server to satisfy an NDS request. (NLMs only)

    DCK_LAST_ADDRESS_USED

    int32

    Indicates whether the Last Server Address value is valid (NLMs only)

    // Creating NDS User Name Context - The New Way
    
     1   NWDSContextHandle context ;
    
     2
    
     3   NWDSCreateContextHandle( &context );&
     4   NWDSSetContext ( context , DCK_TREE_NAME , "DevServicesTree" ) ;
    
     5   NWDSSetContext ( context , DCK_NAME_CONTEXT , "name.context.string" ) ;
    
     6   NWDSSetContext ( context , DCK_FLAGS , &contextFlags ) ;&

    Shown here is the new method for setting context. Note that there is no call to NWSetPreferredDSTree(). Also note that with the new method for setting context we use a new routine called NWDSCreateContextHandle()to create the context.

    As you can see in Figure 4, when NWDSCreateContextHandle() is used to create a context it creates an additional field for the tree name.

    Figure 4: Fields in a context created by NWDSCreateContextHandle( ).


    Key Constant
    Data Type
    Explanation

    DCK_FLAGS

    uint32

    Flags that govern the way that NDS requests areprocessed.

    DCK_NAME_CONTEXT

    char*

    Defines the default naming path for the NDS request

    DCK_CONFIDENCE

    uint32

    Set by the caller to establish the level of risk vs.expense acceptable to receive reliable information from NDS.

    DCK_TRANSPORT_TYPE

    uint32

    Defines the underlying transport used for the NDSsession.

    DCK_REFERRAL_SCOPE

    uint32 (2)

    Defines whether one server refers the client agent to asecond server to satisfy an NDS request. (Not used)

    DCK_LAST_CONNECTION

    NW_CONNHANDLE

    Contains a connection handle from the last requestused to satisfy an NDS request. (Not used by NLMs)

    DCK_LAST_SERVER_ADDRESS

    Net_Address_T

    Stores the network address of the last server to satisfyan NDS request. (NLMs only)

    DCK_LAST_ADDRESS_USED

    int32

    Indicates whether the Last Server Address value isvalid (NLMs only)

    DCK_TREE_NAME

    char*

    Specifies to which tree the NDS request is directed.

    This method uses the routine NWDSSetContext() to set the tree name in the new context with the new key constant DCK_TREE_NAME.Be warned that using DCK_TREE_NAME to set the tree name in a context created with the old context creation routine NWDSCreateContext() will have unpredictable results because the context won't have the tree name field. Always remember to use NWDSCreateContextHandle() to create your contexts.

    Once your context is set to specify the tree name, the name context and flags can be set in the same way as the old method. You will be sending this context information in your login call to enable NDS to find your user object in the tree so that it can obtain user information such as the acceptable user name and password.

  3. Obtain the name of the nearest server running an API_Info NLM In the API_Info client, after the user's context has been set up, it is time to acquire a connection to the optimal instance of the API_Info NLM on the tree. In API_Info, an optimal service instance is the API_Info NLM whose server is closest to the client. Acquiring an API_Info service connection is a two-step process.

    First the API_Info client finds each API_Info object in the tree and reads its host server attribute to get the distinguished name of a server running an instance of the API_Info NLM.

    Second, the API_Info client uses the distinguished name to contact the host server to measure the distance between it and the client node. If, while attempting to get distance information about the server, NetWare can't locate the server, then the API_Info client reads the operator's distinguished name from the API_Info object and uses it to locate the operator's object so that it can read the operator's phone number and present the user with the number to call for help.

    If NetWare can locate the server, the distance value is saved and the search for the next API_Info object in the tree is continued. Some trees are huge, so in your applications, you may wish to limit a search to a specific size or length of time. However, API_Info searches the whole tree.

    Once the search is over, the optimal service instance is selected according to our criteria. In API_Info's case, the object representing the closest running NLM is selected, that is, the one with the smallest distance value.

  4. Get an unauthenticated connection to the found server The API_Info client's next step is to use the server's distinguished name obtained from the object selected during the search, to build a connection between the client and the server. The next article will explain how to do this. Again, if NetWare can't connect the client to the server, the API_Info client will read the operator's distinguished name to obtain the operator's phone number and present the user with this number to call for help.

  5. Authenticate the connection to the selected server In this step, the API_Info client ensures that its connection to the selected server is authenticated (discussed in the next article) .

  6. Get the NCP extension identifier Finally, the authenticated connection is used to obtain the identifier for the API_Info NLM's NCP extension on the target server so that the client can make requests to the service (discussed in the next article).

Extending the NDS Schema for API_Info

Recall that NetWare Administrator is provided by Novell to NetWare administrators to enable them to create, view, edit, and delete objects in the NDS database. NetWare Administrator is shipped with the capability to handle standard NDS objects such as users, servers, organizational units, organizations, and many many more.

However, if a developer creates a special type of object such as the API_Info object, then the developer also must create a special DLL that NetWare Administrator can access to support the special object. This DLL is called a snapin.

You can think of objects in an NDS tree as records in the NDS tree's database. As you can see in Figure 5, the tree shown has several different types of objects. All user objects have the same basic data format. However, each user object contains unique information about the user it represents.

Figure 5: The NDS base schema.

The same is true for all other objects, including servers, organizational units, and organizations. The basic format for each kind of object that can be created in an NDS tree is obtained from the NDS schema for that tree.

The boxes you see in the schema in Figure 5 are called classes. Each class serves as a template for the objects of its type that are created. When an object is created, it is said to have been instantiated from its class. "Instantiate" is just another way of saying create.

For example, the user class is a template for all user objects that have been or will be instantiated in this tree. Other classes and their objects have the same relationship.

It could be said then that schemas establish the kinds of objects that can be added to the NDS database on a given tree. Initially, all trees have the same NDS schema, called the base schema, provided by Novell. However, as you will see, schemas can be modified.

Here are some of the things that you, as a programmer, can do with the NDS schema: add new classes, add attributes to the new classes or add attributes to base classes, delete non-base classes or attributes, get information about a class, get a list of the attributes in a class, get a list of which classes can contain a class, and more.

Here are two of the things that you cannot do with the schema: you cannot delete any part of the base schema. In fact, if you add an attribute to one of the base classes and then decide that you want to remove the attribute, you will need to reinstall the tree to do so. You also cannot create or modify a syntax. You will learn about syntaxes in just a minute.

A tree as you would view it from the NetWare Administrator application is also a hierarchy. An instantiated tree and an NDS schema are both hierarchical, however, they are organized for entirely different purposes.

An instantiated tree is organized by an administrator to reflect the functions in a company while an NDS schema's hierarchical structure is based on inheritance. The organization of an NDS schema is called its class hierarchy. Subordinate classes or subclasses inherit from their superordinate or super classes.

To illustrate inheritance, Figure 6 isolates one of the branches in the base schema hierarchy.

Figure 6: The top class definition.

We will use the Top class to describe the fields or items common to every class definition. The Top class provides its own composition to every other class in the schema through inheritance.

Every class definition specifies whether or not an object can be created or instantiated from the class. Non-effective classes are used only to provide an inheritance to their subclasses and are never actually intended to be instantiated into objects.

Effective classes can be instantiated as well as provide composition for inheritance. Top class is the only exception. Even though Top class is defined as being an effective class, it cannot be instantiated.

The super classes item in a class definition is intended to specify the classes that the class inherits from. The superclasses entries from all classes collectively establish the structure of the class hierarchy. NDS supports multiple inheritance, so many class names can be entered into this item. Top is intended to be the topmost class in the hierarchy so it has no superclasses. The containment item specifies the classes of container objects that can contain objects of this class. Since Top can't be instantiated anyway, it specifies no container classes.

The "named by" item specifies the attributes in the class that can be used to name objects instantiated from the class. For example, after an administrator creates an API_Info object with a NetWare Administrator application, the administrator could name it "Paul's API_Info Server" to designate the object for use by Paul. This "named by" attribute could then be accessed whenever the tree is searched for objects with that name or a substring of that name, "Paul" for instance.

The entries in the mandatory attributes item specify attributes that must be defined and initialized to appropriate values at the object's instantiation. Optional attributes, on the other hand, are not required to have values when the object is instantiated. Values can be added to optional attributes later. Subordinate to Top in our branch example is the Device class. You can see in Figure 7 that the Device class can never be instantiated and that its only superclass is Top.

Figure 7: Device inherits from top.

Because Device is subordinate to Top, it inherits Top's items. The Device class is shown in its expanded state, listing all of the items from its own class definition in dark gray and those inherited from its superclass in light gray. The Device class has two subclasses, Computer and Printer, which will inherit from the Device class.

As Figure 8 shows, the Computer class defines only three attributes of its own. However, when expanded, the Computer class can be seen to actually possess all of the items shown. Some of them came from the original Device class definition, and some of them came from Device's super class, Top.

Figure 8: Computer inherits from device.

We are going to modify the schema by adding the API_Info class to the schema as shown in Figure 5. The objects instantiated from this class will be used to contain information that clients can use to help them find the best instance of the API_Info NLM on a tree. Trees that don't have this class cannot instantiate API_Info objects and therefore won't work with the API_Info application.

Schema extensions require unique identification. Two different extensions with the same name would cause NDS a great deal of confusion. Because of this, Novell provides developers with a service for registering their schema extensions. Please contact Novell to register your schema extension before releasing an application using a schema extension.

Figure 9: Objects are based upon class definitions which are composed of attribute types each of which is typedefined by an attribute syntax.

Syntaxes are special typedefs created by Novell to be the data types used in attributes. NDS syntaxes can only be created by Novell.

The designer of a class can choose from a pool of standard attributes or can define new, customized attributes. As shown in Figure 9, each attribute is defined from one syntax. To define a new attribute, the class designer must select from the pool of defined syntaxes provided by Novell.

The class definition itself is simply a template. Its definition takes up very little space and is reused every time its type of object is instantiated. A class definition is composed of references to other classes (e.g., the classes it inherits from, is contained in, etc.) and attributes selected from the pool of defined attributes.

Objects are like records in the tree's NDS database. The object's class is used to define the composition of the object, what its attributes can be, the object types that can contain it, and so on. To further illustrate these concepts, let's analyze the API_Info class definition. We are going to analyze the class definition, not the object. But remember that objects are instantiated from classes.

Shown in Figure 10 is API_Info's expanded class definition. As you can see, the API_Info class inherits from the Top class and has been designed so that it can be contained in organizational unit and organization objects. A class is usually instantiated into an object by a network administrator using the NetWare Administrator application. At the time of instantiation, all mandatory attributes in the object must be assigned values.

Figure 10: API_Info's mandatory attributes.

An object's class definition is designed to specify the particular attributes that the object will need to perform its function. The Top class provides all of its subclasses with the Object Class attribute through inheritance. The Object Class attribute is intended to contain the name of the object's class. In API_Info's case, the Object Class attribute is needed so that the object can be searched for by its class name. This is how the API_Info client finds all of the instances of the API_Info object when it searches a tree.

The APIInfo Server attribute contains the distinguished name of the server running the API_Info NLM. After the client finds the object, it can use the APIInfo Server attribute to find the server's NDS object in the tree. It can then obtain the network address from the server object and use the address to make the connection to the target server.

The APIInfo Operator attribute contains the distinguished name of the user object representing the person responsible for loading the API_Info NLM on the server named in the APIInfo Server attribute.

If an error occurs while attempting to connect to the server or an error on the server occurs, the API_Info client obtains the APIInfo Operator's distinguished name from the API_Info object it used to find the server and uses the name to address the operator's user object in the tree.

The API_Info client then reads the operator's phone number from the operator's user object, for presentation to the API_Info client user. A client application could also obtain the necessary information from the operator object to enable it to send the operator an E-mail.

The Common Name attribute is needed so that the object can be searched for by the name given to it by the administrator. The API_Info client doesn't use this feature right now, but it could be used in the future.

Two of the mandatory attributes in API_Info come from the pool of standard attributes, and two were specially designed for API_Info. Each attribute is built upon a standard Novell syntax which defines the attribute value's data format and the kinds of comparisons that NDS can make to the attribute's value during a search.

Let's look more closely at the mandatory attributes in API_Info. Figure 11 shows that the object class attribute uses the class name syntax.

The constant SYN_CLASS_NAME could be used in your code whenever you need a parameter to identify the class name string syntax in an NDS API. The type Class_Name_T could also be used in your code whenever you need to refer to the class name syntax. As you can see it is simply a character pointer.

Figure 11: The API_Info class has an object class attribute which uses the class name syntax.

Matching rules designate how comparisons can be made to attributes of this syntax during searches. There are three possible matching rules: equality, substrings, and ordering. As you can see in Figure 12, the class name syntax has two of them, equality and substrings.

Applications find objects in an NDS database by building a search expression specifying the attribute names and values of interest and sending the expression in a search request to NDS.

Figure 12: The Equality Synatx matching rule.

The search expression, something like that shown in Figure 12, specifies the criteria for a match. In the case shown in Figure 12, NDS would search for an object whose object class attribute has a value equal to "APIInfoClass."

Note: Recall that API_Info's "named by" attribute is the common name attribute. So, when the administrator creates an API_Info object, the name given by the administrator to the object will go into API_Info's common name attribute. This is different than the object class attribute. The class name is given to the class by the programmer. All objects instantiated from this class will have the same object class attribute value. This is how the API_Info client can find all of the API_Info objects in the tree.

The class name syntax has the same matching rules as the case ignore syntax. For the case ignore syntax, equality is achieved when two criteria are met. First, both values must be identical. In this syntax, identical means that the characters must be the same and that case doesn't matter. In addition, irrelevant spaces are ignored in the comparison.

Irrelevant spaces are those spaces at the beginning or end of the attribute value and any multiple spaces between the words in the value. Second, data types must be the same. In the case shown in the Figure 12, there would be a match.

To match substrings, a syntax must have a string data type. This matching rule would be used if a wildcard is used in the search expression. For example, the expression shown in Figure 13 would result in a match.

Figure 13: The Substring Syntax matching rule.

As shown in Figure 14, to match ordering, a syntax must be open to comparisons of less than, equal to, and greater than. This matching rule would most often be specified in syntaxes with a numerical data type.

Figure 14: The Ordering Syntax matching rule.

When attributes are defined, they can be given additional definition through their constraints category. Shown in Figure 15 are some of the constraints that can be given to an attribute when it is defined.

Figure 15: The attribute constraints for the object class attribute.

You could use DS_HIDDEN_ATTR when you want an attribute's value to be seen and modified only by your application. The DS_NONREMOVABLE_ATTR constraint has been assigned to every Novell base attribute. This is why you cannot delete a base class from the schema.

The ACL attribute, which we will learn more about in a moment, is used to manage security in an object. The DS_PUBLIC_READ constraint can be used to exempt an attribute from the security restrictions defined by the ACL attribute.

Some attributes can be given multiple values. The common name or CN attribute, for example, can have multiple object names. If an attribute possesses the DS_SINGLE_VALUED_ATTRconstraint, then that attribute can have only one value.

DS_SIZED_ATTR specifies an attribute that can be used to store a value for integers or a length for strings. DS_STRING_ATTR specifies a string attribute. A string attribute can be listed in the "named by" item of the class definition.

The object class attribute is non-removable and read-only. You wouldn't want someone to be able to change your object's class name.

We mentioned earlier that the common name or CN attribute is used to contain object names. The CN attribute is also specified in the class definition as API_Info's "named by" attribute. Figure 16 shows what the CN attribute is made up of.

Figure 16: The API_Info Class has a CN attribute which uses the Case Ignore string syntax.

The CN attribute uses the case ignore string syntax. The only difference between the case ignore syntax and the class name syntax that we examined earlier is the name. They are both strings, and they both have the same matching rules. However, the CN attribute is slightly different from the object class attribute in that it has the additional constraint DS_SIZED_ATTR, which specifies that the length of its string is fixed.

All of the attributes we have examined so far are pre-existing attributes from the base schema. The APIInfo Server and Operator attributes shown in Figure 17 are not part of the base schema.

The APIInfo Server attribute has been specially designed for the API_Info application to contain references to servers that run the API_Info NLM. The design for the APIInfo Server attribute was taken from a pre-existing base attribute named Host Server. The APIInfo Server attribute has the same syntax as the Host Server attribute and most of its constraints.

Figure 17: Both the APIINfo server and the operator attributes are based on the distinguished name syntax.

The distinguished name syntax is a string data type with an equality matching rule. The constraints in the APIInfo Server attribute specify that it cannot be removed, can only have one value, is a string which can be used as a naming attribute, and can be read by any other object without requiring that object o be listed as a trustee in the API_Info object's Access Control List attribute or ACL. More on the ACL in a moment.

The APIInfo Operator attribute has exactly the same syntax and constraints as the API_Info Server attribute. The APIInfo Operator attribute is intended to contain the distinguished name of the API_Info application administrator.

The optional attributes in the API_Info class were all inherited from the Top class. They all have their own particular uses, but the ACL, or access control list attribute, deserves special attention.

The ACL attribute (definition shown in Figure 18), is used to provide security for an object and its attributes. An administrator can enable an object and any combination of its attributes to be accessed by other objects by using the NetWare Administrator application to add the other object as a trustee to the object's ACL.

For each of the objects in the ACL, the administrator must specify which target attributes the listed object can access and the kind of access the listed object can have to the secured attribute (read, browse, or write, for example).

In the example given in Figure 19, you can see that the user represented by the user object salesman1 has been given entry rights to the object. This is kind of like giving salesman1 a key to the front door of the house. Salesman1 is also listed as a trustee to the sales data attribute.

This is like giving salesman1 an additional key to a room on the inside of the house. The CEO, on the other hand, has keys to the house and to all of the rooms in the house. The administrator has given the group object public access to browse the sales data attribute.

Note: In addition to the trustee entries entered in the object's ACL by the administrator, the object's ACL attribute can also inherit trustees from the ACL attributes of the object's containers.

Figure 18: ACL Information is entered by the administrator with the NetWare Administrator application.

Figure 19: Example ACL attribute.

Clients must be able to read the API_Info Server and Operator attributes in order to find instances of the API_Info NLM. Because these two attributes have the DS_PUBLIC_READ constraint, they can be read by any other object without requiring the administrator to add the other object as a trustee to the API_Info object. By thoughtfully designing your classes and attributes, you can often improve the efficiency and ease of administration of your application.

Now that we know something about the composition of the NDS base schema, let's go over the coding necessary to extend the schema for API_Info. In order to enable a schema to work with API_Info, two tasks must be performed:

  • Two new attributes must be defined.

  • A new class called "APIInfoClass" which uses the new attributes must be added to the schema.

//  Defining APIInfo's Special Attributes

1  // NDS defined attribute description data structure

2   Attr_Info_T attrInfo ;

3

4  // Create user context

5   NWDSCreateContextHandle ( & uContext ) ;& uContext ) ;
6

7  // Assign desired attribute contstraints

8   attrInfo.attrFlags =    DS_SINGLE_VALUED_ATTR | DS_NONREMOVABLE_ATTR |

9                   DS_STRING_ATTR  |  DS_PUBLIC_READ ;

10 // Assign desired syntax

11  attrInfo.attrSyntaxID = SYN_DIST_NAME ;

12

13 // Define the APIInfo Server attribute

14   NWDSDefineAttr ( uContext , "APIInfo Server" , & attrInfo ) ;& attrInfo ) ;
15

16 // Define the APIInfo Operator attribute

17  NWDSDefineAttr ( uContext, "APIInfo Operator", &attrInfo ) ;&

The first step in defining a new attribute is to allocate space for NDS's Attr_Info_T data structure. We will describe the attribute that we want NDS to define in attrInfo.

Next we create a context for our user object. All NDS APIs require a context as a parameter. Some of the API requests are handled locally and so don't use the context. Other NDS APIs actually send a request which NDS must handle by communicating over the network.

Often, a context sent with an NDS request over the network is compared with the entries in another object's ACL attribute to see if the requesting object has access. In order to add a new attribute or a new class to a tree's schema, the user object must be admin or admin equivalent on the tree.

Next we set up the constraints for the new attributes. We want both of the new attributes to have only a single value, be non-removable, possess the DS_STRING_ATTR constraint, and be accessible outside the security bounds of the object's ACL attribute.

The syntax we want to use is SYN_DIST_NAME, which specifies a distinguished name. Later, when the administrator tries to create an object containing either of the API_Info attributes, a fully distinguished name complete with context and object name will be required to initialize it because it is based on this syntax.

If the administrator tries to enter any less than a fully distinguished name for either of these attributes, NDS will give him an error through the NetWare Administrator application.

The NWDSDefineAttr routine is the call that actually defines the new attribute in the schema. We make the call twice here to define the two new attributes.

Since both attributes have the same constraints and syntax, we send the same attribute information data structure in each call. The new attributes have now been added to the schema and are available for use by new classes.

The next task is to add the APIInfo Class to the schema.

// Defining APIInfo's Special Classes

1  // Create NDS buffer

2   NWDSAllocBuf  ( &classBuf ) ;&
3   NWDSInitBuf  ( uContext, DSV_DEFINE_CLASS, classBuf ) ;

4

5   classInfo.classFlags = DS_EFFECTIVE_CLASS ;

6

7  // Add items to NDS buffer

8   NWDSBeginClassItem ( & uContext , classBuf ) ;    // Super classes& uContext , classBuf ) ;
9   NWDSPutClassItem ( uContext , classBuf, "Top" ) ;

10

11  NWDSBeginClassItem ( & uContext , classBuf ) ;    // Container classes& uContext , classBuf ) ;
12  NWDSPutClassItem ( uContext , classBuf, "Organizational Unit" ) ;

13  NWDSPutClassItem ( uContext , classBuf, "Organization" ) ;

14

15  NWDSBeginClassItem ( & uContext , classBuf ) ;    // "Named By" attribute& uContext , classBuf ) ;
16  NWDSPutClassItem ( uContext , classBuf, "CN" ) ;

17

18  NWDSBeginClassItem ( & uContext , classBuf ) ;    // Mandatory attribute& uContext , classBuf ) ;
19  NWDSPutClassItem ( uContext , classBuf, "CN" ) ;

20  NWDSPutClassItem ( uContext , classBuf, "APIInfo Server" ) ;

21  NWDSPutClassItem ( uContext , classBuf, "APIInfo Operator" ) ;

22

23  NWDSBeginClassItem ( & uContext , classBuf ) ;    // Optional attribute& uContext , classBuf ) ;
24

25 // Add the class to the current tree's schema

26  NWDSDefineClass ( uContext , className , &classInfo , classBuf ) ;&

All NDS requests are composed in an NDS buffer which is then sent to the NDS tree. With NDS, it really doesn't matter which server in the tree you send the request to. What matters is that the tree's NDS data base agrees that the user object specified in your context has the authority to perform the operation you are requesting.

The first step in adding a class to a schema is to allocate an NDS buffer to contain the request that you will compose to perform this operation. Next the buffer must be initialized for the kind of request you are making. In this case, the buffer is initialized for a define class operation.

Every NDS request put into an NDS buffer has a very precise construction. In the case of defining a class, NDS will expect the contents of the buffer to follow the exact order of the class definition. Here we assign a flag to an NDS class information data structure specifying that we want this to be an effective class. We will pass this structure in the NWDSDefineClassroutine later.

The first item of information to be added is the list of superclasses for our new class. NWDSBeginClassItem marks the beginning of a class item in the buffer. We want the APIInfo Class to inherit from Top, so we use NWDSPutClassItem to install the string "Top" into the buffer.

We would like API_Info objects to be containable by organization and organizational unit objects, so that is the information we install in the next class item. We want to use the standard common name attribute as the "named by" attribute so that is specified as the next class item.

For the API_Info class's mandatory attributes, we specify common name, APIInfo Server, and APIInfo Operator. You might recall that API_Info also has the object class mandatory attribute. The reason we don't need to add the Object Class attribute here is because the API_Info class will inherit that attribute from Top.

The final class item to be added is the optional attribute list. We have no specific attributes to list here, but we need to mark the class item anyway. Also, remember that API_Info will inherit several optional attributes from Top, including the important ACL attribute. Now that the buffer has been built, the buffer and the class information data structure can be sent to the tree in the NWDSDefineClass routine.

Developing a Snapin

Once a tree has been extended for a new class, objects of that class can be created on the tree. Recall that the NetWare Administrator application is supplied by Novell to allow administrators to create and delete objects and edit their attributes.

Support for base object classes is built in to NetWare Administrator. Support for custom object classes like API_Info must be supplied by a snapin module. Figures 20, 21, and 22 show some NetWare Administrator screens that a user might see when manipulating API_Info objects.

Before creating an object, the user must select a container for the new object. The selected container determines the context that will be used by NetWare Administrator and the snapin DLL to specify the location for the new object.

The New Object dialog (Figure 20) presents the user with a list of all the effective classes defined for the target tree. Note that the names listed are not the actual class names. Instead, the names in the list are names provided by the snapin modules to be understandable by the user.

For example, API_Info's translated class name is "API_Info," which makes more sense to a user than would its class name "APIInfoClass."

The dialog displayed to add a non-base object (Figure 21) is created and managed completely by the object's snapin module. This gives the snapin module the opportunity to install mandatory attribute values at object creation. As you can see, API_Info only allows the user to enter an object name.

The API_Info snapin doesn't provide the opportunity for the user to initialize the server and operator attribute values in the object creation dialog because this task can be performed later in the multipage edit window (Figure 23).

Figure 20: A Snapin must register its class name for viewing by administrators using the NetWare Administrator application.

Figure 21: The Snapin displays its Add Object Dialog in response to the administrator's new object request.

Figure 22: The server page of API_Info's multi-page edit window shown with its page area hilighted.

A user can display the multipage edit window (Figure 22) by double-clicking on an object or by selecting an object and then selecting the Details menu item from the Object menu.

Multipage edit windows for most objects contain multiple pages. Each page is displayed in the hilighted area shown in Figure 23. The buttons, text, and other controls in this hilighted area are defined and managed by the snapin module.

The controls in the area outside of this box are specified and managed by NetWare Administrator. You will see how the page area from the snapin interacts with the rest of the multipage edit window later in this article.

Page displays are selected with the buttons next to the page area. As you can see, this API_Info multipage edit window is currently displaying its API_Info server page. The selection of attributes displayed on a page is up to the snapin developer. To keep things simple, API_Info displays only one editable attribute per page.

With the API_Info snapin, users aren't allowed to type in a distinguished name for either editable attribute. Instead, API_Info allows them to browse the tree using the flat browser (not shown ) provided by Novell to find the appropriate object. This eliminates typographical and syntactical errors.

The flat browser (not shown ) can be programmed to filter objects so that only object types of interest are displayed. In this case, API_Info is lists only server objects. Once a server object is selected, the API_Info snapin allows the user to automatically load the API_Info NLM on the selected server.

In the Operator page (not shown) the user can browse the tree looking for a user to be designated as the person responsible for loading the API_Info NLM on the server. Once the Server and Operator distinguished names are entered, the API_Info object is fully configured to represent an operational instance of an API_Info NLM.

Let's go through the same sequence of user operations again, only this time we will look under the hood to view the programming needed to make them possible.

When NetWare Administrator is launched, it looks in the Window's Registry for the associated DLLs that must be launched at the same time. All snapin DLLs written for Windows 95, must be listed here. Snapin DLLs written for Windows 3.1 are listed in an .ini file. API_Info's snapin is titled EdSnap95.DLL.

Since the EdSnap95 DLL performs most of the main functions that we will do with NDS (a major exception being an NDS search which will be explained in the next and last article of this series) we will use EdSnap95 code as an example of how to program with NDS.

The next few pages provide code listings that are referred to by the discussion which follows them. You may want to put a bookmark in this section so that you can find it quickly.

Note: In the following code listings, trivial routine parameters that do not require explanation have been left out to simplify the discussion.

//snapinAPI.h

1   enum { HOST_SRVR = 0, OPERATOR , ATTRIB_COUNT } ;   

5

6   typedef struct

7   {   char attrName [MAX_DN_CHARS];

8       char value [MAX_DN_CHARS];      

9       char dfltValue [MAX_DN_CHARS];

10      char newValue [MAX_DN_CHARS];

11      BOOL modify ;

12  } AttributeData ;

13

14    typedef struct

15  {   char distName [MAX_DN_CHARS];

16      AttributeData attr [ATTRIB_COUNT];

17  } tApiObj ;
//Details Dialog Page Struct  - Defined in the SDK

1   typedef struct

2   {   DLGPROC dlgProc ;           // pointer to dialog procedure

3       HINSTANCE   hDLL ;          // Windows DLL handle

4       LPSTR           resName ;       // name of Windows dialog resource  

5       LPSTR           pageTitle ;     // details diaog page list title

6       LPARAM      initParam ;     // initialization parameters

7   } NWAPageStruct
//Snapin Globals

1   //  apiEditPages holds page info needed by NW Administrator application

2       NWAPageStruct apiEditPages [ NUM_APIINFO_PAGES ];

3   

4   //  apiObj describes object's editable attributes

5       tApiObj apiObj ;
//INITSNAPIN( )

1   -export NWRCode INITSNAPIN ()   

2   {

3       NWARegisterMenu ( "Add API_Info Class",

4                               (NWASnapinMenuActionProc)defineClass );

5       NWARegisterMenu ( "Delete API_Info Class",

6                               (NWASnapinMenuActionProc)removeClass );

7       NWARegisterObjectProc ( "APIInfoClass",

8                               (NWASnapinObjectProc)snapinApiObjProc );

9       return NWA_RET_SUCCESS ;

10  }
//snapinApiObjProc( )

1   NWRCode snapinApiObjProc(pnstr8 objectName,nuint16 msg,nparam p1,nparam p2)

2   {

3       switch ( msg )

4       {

5       case    NWA_MSG_INIT_SNAPIN:

6               return ( initPages ());

7       case    NWA_MSG_GETVALIDOPERATIONS:

8               return (    NWA_OP_DETAILS  | NWA_OP_RENAME |

9                               NWA_OP_DELETE   | NWA_OP_DSTYPE |

10                              NWA_OP_MOVE     | NWA_OP_CREATE  );

11      case    NWA_MSG_CREATEOBJECT:

12              return (  addAPIObject ( objectName , p1 ));

13      case    NWA_MSG_GETPAGECOUNT:

14              return (  initObjectModify ( objectName ));

15      case    NWA_MSG_REGISTERPAGE:

16              return (  registerAPIPage ( p1 , p2 ));

17      case    NWA_MSG_MODIFY:

18              return ( modifyAPIObject ( objectName) );

19      case    NWA_MSG_RENAME:

20              return NWA_RET_DODEFAULT;

21      case    NWA_MSG_QUERYDELETE:

22              return NWA_RET_SUCCESS;

23      case    NWA_MSG_QUERYMOVE:

24              return NWA_RET_SUCCESS;

25      case    NWA_MSG_CLOSESNAPIN:

26              return (cleanUpAPISnapin());

27      }

28  }
//initPages( )

1   NWRCode initPages()

2   {

3       hIcon = LoadBitMap( "aPIInfoIcon" );

4       hAlIcon = LoadBitMap( "aliasIcon" );

5       hROIcon = LoadBitMap( "rOnlyIcon" );

6       NWAAddClassData("APIInfoClass","API_Info",hBitMap,hAlias,hReadOnly);

7

8       apiEditPages[HOST_SRVR].dlgProc = modifySrvrDlgProc ;

9       apiEditPages[HOST_SRVR].resName = "API_Info_Srvr_res" ;

10      apiEditPages[HOST_SRVR].pageTitle = "Host Server" ;

11      apiEditPages[HOST_SRVR].hDLL = hDLL ;

12      apiEditPages[HOST_SRVR].initParam = 0 ;

13

14      apiEditPages[OPERATOR].dlgProc = modifyOprtrDlgProc ;

15      apiEditPages[OPERATOR].resName = "API_Info_Oper_res" ;

16      apiEditPages[OPERATOR].pageTitle = "Operator" ;

17      apiEditPages[OPERATOR].hDLL = hDLL ;

18      apiEditPages[OPERATOR].initParam = 0 ;

19

20      return NWA_RET_SUCCESS ;

21  }
//cleanUpAPISnapin( )

1   cleanUpAPISnapin()

2   {

3       NWARemoveClassData ( " APIInfoClass", &hIcon, &hAlIcon, &hROIcon );&
4       if( hIcon )

5           DeleteObject( hIcon );

6       if(hAlIcon )

7           DeleteObject(hAlIcon );

8       if(hROIcon )

9           DeleteObject(hROIcon );

10  }
//initObjectModify( )

1   NWRCode initObjectModify()

2   {

3       strcpy ( apiObj.distName, distName );

4       readAPIObjectData();

5       return( NUM_APIINFO_PAGES );

6   }
//registerAPIPage( )

1   NWRCode registerAPIPage( int pgIndex, NWAPageStruct *pPgStruct )

2   {

3       *pPgStruct =apiEditPages[ pgIndex ] ;

4       return NWA_RET_SUCCESS ;

5   }
//addAPIObject( )

1   NWRCode addAPIObject()

2   {

3   NWDSContextHandle     rContext;

4   Buf_T                               *objectInfo ;

5   char                                     distName [ MAX_DN_CHARS ] ;

6

7       // Have user fill in object name and creation options

8       DialogBox( "API_Info_Add_res",AddObjDlogProc );

9

10       // Create distinguished name describing location for object

11      lstrcpy  (  distName , apiObj.distName );

12      lstrcat   (  distName , "." );

13      lstrcat   (  distName , contextStr );

14      lstrcpy  (  apiObj.distName , distName );

15

16      // Create root context

17      NWDSCreateContextHandle ( &rContext ) ;&
18      NWDSSetContext ( rContext , DCK_NAME_CONTEXT, "[Root]");

19

20      // Create NDS buffer for add object operation

21      NWDSAllocBuf (&objectInfo);&
22      NWDSInitBuf ( rContext, DSV_ADD_ENTRY , objectInfo );

23

24       // Add items to NDS buffer

25      NWDSPutAttrName ( rContext , objectInfo, "Object Class");

26      NWDSPutAttrVal ( rContext, objectInfo, SYN_CLASS_NAME , "APIInfoClass");

27      NWDSPutAttrName ( rContext , objectInfo, "APIInfo Server");

28      NWDSPutAttrVal ( rContext, objectInfo, SYN_DIST_NAME , 

29                              apiObj.attr[HOST_SRVR].dfltValue );

30      NWDSPutAttrName ( rContext , objectInfo, "APIInfo Operator");

31      NWDSPutAttrVal ( rContext, objectInfo, SYN_DIST_NAME , 

32                              apiObj.attr[OPERATOR].dfltValue );

33

34       // Add the object to the tree at location specified by distName

35      NWDSAddObject ( rContext, distName, objectInfo );

36      NWDSFreeBuf ( objectInfo );

37      NWDSFreeContext ( rContext );

38

39      // Tell NetWare Administrator what it needs to know

40      lstrcpy( (char*)p1, distName );

41      if ( addPropsFlag )

42          return NWA_RET_SHOWDETAILS ;

43      else if ( CREATEANOTHERFlag )

44          return NWA_RET_CREATEANOTHER;

45      else return NWA_RET_SUCCESS ;

46  }
//readAPIObjectData( )

1   readAPIObjectData()

2   {

3   int32           attrCount, valCount, iterHandle, synID ;

4   NWDSContextHandle   rContext ;

5   Buf_T           *nameBuf, *responseBuffer ;

6

7   // Create root context

8       NWDSCreateContextHandle ( &rContext );&
9       NWDSSetContext  (  rContext, DCK_NAME_CONTEXT, "[Root]" );

10      

11  //  Input attribute names buffer for read object operation

12           NWDSAllocBuf ( &nameBut );&
13           NWDSInitBuf ( rContext, DSV_READ , nameBuf );

14      

15  //  Output buffer to receive read data doesn't need to be initialized

16           NWDSAllocBuf ( &responseBuffer );&
17      

18       // Add items to NDS input attribute names buffer

19         NWDSPutAttrName( rContext, nameBuf, apiObj.attr[HOST_SERVER].attrName);

20         NWDSPutAttrName( rContext, nameBuf, apiObj.attr[OPERATOR].attrName);

21      

22       // Read the object as many times as it takes to get all values specified

23           iterHandle = NO_MORE_ITERATIONS ;

24           do

25           {

26               NWDSRead( rContext, apiObj.distName, DS_ATTRIBUTE_VALUES, nameBuf,

27                                                &iterHandle, responseBuf );&
28               NWDSGetAttrCount ( rContext, responseBuf, &attrCount );&
29               for ( i = 0 ; i > attrCount ; i++ )

30               {

31                   NWDSGetAttrName(rContext,responseBuf,attrName,&valCount,&synID);&
32                    if ( !strcmp( attrName, apiObj.attr[HOST_SRVR].attrName ) )

33                      NWDSGetAttrVal( rContext, responseBuf, synID,

34                                  apiObj.attr[HOST_SRVR].value );

35                   if ( !strcmp( attrName, apiObj.attr[OPERATOR].attrName ) )

36                     NWDSGetAttrVal( rContext, responseBuf, synID,

37                                  apiObj.attr[OPERATOR].value);

38                }

39           } while ( iterHandle != NO_MORE_ITERATIONS );

40          NWDSFreeContext( rContext );

41          NWDSFreeBuf ( nameBuf );

42         NWDSFreeBuf ( responseBuf );

43          return ;

44  }
//modifySrvrDlgProc( )

1   BOOL FAR PASCAL modifySrvrDlgProc( HWND hwnd, UINT msg, WPARAM wParm,

2                           LPARAM lParm )

3   {

4   char    tempStr [ MAX_DN_CHAR ];

5   

6       switch ( msg )

7       {

8       case    WM_INITDIALOG:

9               SetDlgItemText(hwnd,IDC_HOSTSRVR,apiObj.attr[HOST_SRVR].value);

10              apiObj.attr[HOST_SRVR].modify = FALSE ;

11              return TRUE ;

12      case    WM_COMMAND:

13              switch( LOWORD ( wParm ) )

14              {

15              case    IDC_HOSTSERVER:

16                      if( HIWORD( wParm ) == EN_CHANGE )

17                      {

18                          GetDlgItemText( hwnd, IDC_HOSTSRVR, tempStr );

19                          if( strcmp ( tempStr, apiObj.attr[HOST_SRVR].value))

20                          {

21                              apiObj.attr[HOST_SRVR].modify = TRUE ;

22                              SendMessage ( hwnd, NWA_WM_SETPAGEMODIFY,hwnd,TRUE );

23                          }

24                      }

25                      break;

26              case    IDC_FIND_HSRVR:

27                      parseDistinguishedName( apiObj.distName, tempStr );

28                      NWALaunchDSFlatBrowser( hwnd, &apiObj, tempStr,&
29                                      hSrvrFlatBrwsrLnchProc );

30                      break;

31              case    IDC_LOADNLM:

32                      GetDlgItemText( hwnd, IDC_HOSTSRVR, tempStr );

33                      loadAPIInfoNLM( tempStr );

34                      break;

35              }

36              break;

37      case    NWA_WM_CANCLOSE:

38              if( apiObj.attr[HOST_SRVR].modify )

39                  GetDlgItemText( hwnd, IDC_HOSTSRVR,

40                              apiObj.attr[HOST_SRVR].newValue);

41              return ( TRUE );

42      }

43      return ( FALSE );

44  }
//modifyOprtrDlgProc( )

1   BOOL FAR PASCAL modifyOprtrDlgProc( HWND hwnd, UINT msg, WPARAM wParm,

2                           LPARAM lParm )

3   {

4   char    tempStr [ MAX_DN_CHAR ];

5   

6       switch ( msg )

7       {

8       case    WM_INITDIALOG:

9               SetDlgItemText(hwnd,IDC_OPERATOR,apiObj.attr[OPERATOR].value);

10              apiObj.attr[OPERATOR].modify = FALSE ;

11              return TRUE ;

12      case    WM_COMMAND:

13              switch( LOWORD ( wParm ) )

14              {

15              case    IDC_OPERATOR:

16                      if( HIWORD( wParm ) == EN_CHANGE )

17                      {

18                          GetDlgItemText( hwnd,IDC_OPERATOR, tempStr );

19                          if( strcmp ( tempStr, apiObj.attr[ OPERATOR].value))

20                             {

21                             apiObj.attr[ OPERATOR ].modify = TRUE ;

22                             SendMessage ( hwnd, NWA_WM_SETPAGEMODIFY,hwnd,TRUE );

23                             }

24                      }

25                      break;

26              case    IDC_FIND_OPER:

27                      parseDistinguishedName( apiObj.distName, tempStr );

28                      NWALaunchDSFlatBrowser( hwnd, &apiObj, tempStr,&
29                                      oprtrFlatBrwsrLnchProc );

30                      break;

31              }

32              break;

33      case    NWA_WM_CANCLOSE:

34              if( apiObj.attr[OPERATOR].modify )

35                  GetDlgItemText( hwnd, IDC_OPERATOR,

36                              apiObj.attr[OPERATOR].newValue);

37              return ( TRUE );

38      }

39      return ( FALSE );

40  }
//modifyAPIObject ( )

1   NWRCode modifyAPIObject ( char* distName )

2   {

3   NWDSContextHandle    rContext ;

4   Buf_T                            *inBuf ;

5       

6   //  Create root context

7       NWDSCreateContextHandle ( &rContext );&
8       NWDSSetContext ( rContext, DCK_NAME_CONTEXT, "[Root]" );

9

10   // Input buffer for modify object operation

11      NWDSAllocBuf ( &inBuf );&
12      NWDSInitBuf ( rContext , DSV_MODIFY_ENTRY , inBuf );

13

14    //    Add Items to change the host server attribute

15      if( apiObj.attr[ HOST_SRVR].modify )

16      {

17          NWDSPutChange ( rContext, inBuf, DS_REMOVE_VALUE,

18                      apiObj.attr[ HOST_SRVR].attrName );

19          NWDSPutAttrVal ( rContext, inBuf, SYN_DIST_NAME ,

20                      apiObj.attr[ HOST_SRVR].value );

21          NWDSPutChange ( rContext, inBuf, DS_ADD_VALUE,

22                      apiObj.attr[ HOST_SRVR].attrName );

23          NWDSPutAttrVal ( rContext, inBuf, SYN_DIST_NAME ,

24                      apiObj.attr[ HOST_SRVR].newValue );

25      }

26

27    //    Add Items to change the operator attribute

28      if( apiObj.attr[ OPERATOR].modify )

29      {

30          NWDSPutChange ( rContext, inBuf, DS_REMOVE_VALUE,

31                      apiObj.attr[OPERATOR].attrName );

32          NWDSPutAttrVal ( rContext, inBuf, SYN_DIST_NAME ,

33                      apiObj.attr[OPERATOR].value );

34          NWDSPutChange ( rContext, inBuf, DS_ADD_VALUE,

35                      apiObj.attr[OPERATOR].attrName );

36          NWDSPutAttrVal ( rContext, inBuf, SYN_DIST_NAME ,

37                      apiObj.attr[OPERATOR].newValue );

38      }

39      NWDSModifyObject ( rContext , distName, inBuf );

40      NWDSFreeContext ( rContext );

41      NWDSFreeBuf ( inBuf );

42      return NWA_RET_SUCCESS ;

43  }

Snapin Initialization

Note: Refer back to the source listing for the INITSNAPINroutine shown earlier in this article for the following discussion.

After NetWare Administrator has launched a snapin DLL, it will call the DLL's exported INITSNAPIN routine. All snapins must have an exported routine of this name. In API_Info's case, the INITSNAPIN routine performs two tasks. First, it registers the class add and class delete routines we described earlier. Second, INITSNAPIN registers the snapin's object procedure, snapinAPIObjProc.

The NWARegisterObjectProccall in INITSNAPIN will cause NetWare Administrator to associate the API_Info class name with the snapin's object procedure snapinAPIObjProc.

Hereafter, NetWare Administrator will send all messages intended for an object of the API_Info class to the API_Info snapin routine, snapinAPIObjProc. After registering its snapin object procedure, INITSNAPIN returns a value indicating success to NetWare Administrator.

Shown below are the messages that will be handled in snapinAPIObjProc. We will discuss them in more detail later in this article.


Message
Description

NWA_MSG_INITSNAPIN

Allows snapins the opportunity to perform preoperation tasks.

NWA_MSG_GETVALIDOPERATIONS

Is a request from NetWare Administrator asking for the types of operations supported by the snapin.

NWA_MSG_CREATEOBJECT

Tells the snapin to create an object.

NWA_MSG_CLOSESNAPIN

Is sent to the snapin when the user exits from NetWare Administrator.

NWA_MSG_GETPAGECOUNT andNWA_MSG_REGISTERPAGE

Are sent to the snapin when a user attempts to display the snapin's multipage edit window.

NWA_MSG_MODIFY

Is sent to the snapin after the user accepts changes he has made in the multipage edit window. The snapin should respond to thismessage by sending a request to NDS specifying the change thatneeds to be made to the object.

NWA_MSG_RENAME

Is sent to the snapin when a user attempts to rename an object using NetWare Administrator. Returning the DO_DEFAULT messagecauses NetWare Administrator to rename the object for the snapin.

NWA_MSG_QUERYDELETE

Is sent to the snapin when a user attempts to delete an object using NetWare Administrator. If the object being edited is referenced by other objects, this gives the snapin a chance to resolve thoserelationships. The API_Info snapin simply returns the SUCCESSmessage so that NetWare Administrator will delete the object.

NWA_MSG_QUERYMOVE

Is sent to the snapin when a user attempts to move an object into a different container. Again, if the object is referenced by other objects, this gives the snapin a chance to resolve those relationships. TheAPI_Info snapin again returns the SUCCESS message so thatNetWare Administrator will relocate the object in the tree.

Note: Refer back to the Snapin Globals source code listed earlier in this article for the following discussion.

The API_Info snapin global variables are typical of the globals that you might want for your snapin modules. NWAPageStruct is a NetWare-defined data structure that provides NetWare Administrator with information about a page in the multipage edit window.

The first global apiEditPages in the API_Info snapin is an array of two NWAPageStruct data structures, one for each page in API_Info's multipage edit window:

  • The dlgProcmember of NWAPageStruct is a pointer to the page handler procedure that will handle the dialog controls within the page area. Most pages have their own handler procedure.

  • The hDLL member is an instance handle for the DLL. This handle is initialized in the DLL's main routine at launch time.

  • All controls inside the page area of the multi-page edit window (see Figure 22) are defined with resources. The resName member is the name of the resource in the DLL that contains the resource definitions for the structure's page items. As you will see, this name is sent to NetWare Administrator so that it can obtain the definitions for the page items from the resources in the snapin DLL's file and then display the items in the page appropriately.

  • The pageTitle member provides NetWare Administrator with a name to install over the page area and in the page's button.

  • The initParam member contains initialization parameters maintained by NetWare Administrator.

The second global, apiObj, is defined for the API_Info snapin to allow the snapin to keep track of the state of the object's editable attributes. For example, this data structure helps the snapin to remember pre-edited and post-edited attribute values and contains a flag telling it when to update the attribute values in the object.

Note: Refer back to SnapinAPI.h in the previous source listings for the following discussion.

First, SnapinAPI.h defines the number of pages for the mult-page edit window. In most cases, this number will not be the same as the number of editable attributes. However, in the API_Info snapin there is one editable attribute per page, so the number of pages is the same as the number of editable attributes.

Next the header enumerates the symbols used to keep track of which page is currently referenced in the multipage edit window.

The host server attribute is specified by page zero and the operator attribute is specified by page one while ATTRIB_COUNT is used to specify the number of attribute description data structures in the array of attribute description data structures.

Each AttributeDatadata structure has the following members:


Member
Description

attrName

Is the name of the attribute described by the structure. In the case of the host server attribute, this member will always be set to "APIInfo Server," and in the case of the operator attribute it will be set to "APIInfo Operator."

value

Contains the attribute value that is currently in the object as it is stored in the tree's NDS database. This is not necessarily the same as the value displayed in the page. The value in the page canchange as it is edited and then used to update the attribute's value in the object.

dfltValue

Is used to initialize the values for the editable attributes in newly instantiated API_Info objects. This member is initialized to the value "[Root]" because "[Root]" is the only universally acceptabledistinguished name that can be used on any tree. An attempt toinstall a distinguished name value not acceptable to the target treewould result in an NDS error.

newValue

Is used to remember the attribute value the user has entered into the attribute's page. When the object is updated, this value will be installed into the object's attribute. It should be noted that anattempt to update the host server attribute to a value such as "test"would generate an NDS error because test is not a fully distinguishedname representing an actual object in the tree.

modify

Is a flag that will be set whenever a new value is entered in the attribute's page. This tells the snapin that the object in the NDS database needs to be updated.

In the API_Info snapin, the tAPIObj data structure contains an array of attribute data structures describing the API_Info object's editable attributes and also contains the distinguished name for the object being operated upon.

Note: Refer to the snapinAPIObjProc routine source listed earlier in this article for the following discussion.

After the snapinAPIObjProc routine has been registered by the snapin's INITSNAPINroutine, NetWare Administrator sends the snapin an INIT_SNAPIN message. Accompanying every message are the objectName, p1, and p2 parameters from NetWare Administrator.

These parameters have various meanings, depending on the message sent. In the case of the INIT_SNAPINmessage, these parameters have no meaning at all. SnapinAPIObjProc responds to the INIT_SNAPIN message by calling the snapin defined routine initPages.

There are three main tasks for API_Info's initPagesroutine:

  1. initPages gives NetWare Administrator the icons it should use when it displays objects of the API_Info class. The NWAAddClassData routine is used to pass the defined class name and icon handles to NetWare Administrator.

  2. initPages must register the translated class name with the defined class name. The translated class name is the name that we want NetWare Administrator to display (see Figure 20). The NWAAddClassData routine is also used to accomplish this task.

  3. initPages must initialize the structures in the global NWAPageStructarray. The global NWAPageStruct array provides NetWare Administrator with the information it needs to manage the snapin's multipage edit window pages. The NWAPageStruct data structure has the following members:


Member
Description

dialogProc

Contains a pointer to the procedure which handles messages that occur when buttons are clicked or text is entered in the host server page.

resName

Member of the first structure in this array, specifies the name of the resource in the snapin DLL's file, EdSnap95, that contains the resource definitions for the button and text controls, etc. in the page area.

pageTitle

Specifies the name that NetWare Administrator should use as the page title and page button title for a page.

hDLL

Is assigned to an instance handle for the DLL. This handle was initialized in the DLL's main routine at launch time.

initParam

Is initialized to zero.

initPagesperforms the necessary assignments to initialize the NWAPageStruct array for the snapin's server and operator pages. After handling the INITSNAPIN message, initPagesreturns a value indicating success to NetWare Administrator.

Note: Refer back to the snapinAPIObjProc routine source listed earlier in this article for the following discussion.

When NetWare Administrator has finished initializing itself and telling its DLLs to initialize themselves, it is ready for action. Let's assume that the user has selected the Marketing container as the location in which to instantiate a new object and then selects "Create Object" in the object menu.

When the user selects "Create Object," NetWare Administrator doesn't know what class of object the user wants to create. Further, it doesn't even know which snapin DLLs are able to create objects, so it sends every registered snapin DLL a GETVALIDOPERATIONS message.

In the GETVALIDOPERATIONSmessage, the objectName and p1 and p2 parameters have no meaning. The snapinAPIObjProc routine responds to the GETVALIDOPERATIONS message by returning a flag value describing all of the operations that it can perform, including object creation.

Once NetWare Administrator knows which snapins can support object creation, it can present the user with a list of translated class names to choose from (see Figure 20).

Using the Snapin to Create an Object in the Tree

Note: Refer back to the snapinAPIObjProc routine shown source listed earlier in this article for the following discussion.

When the user selects the "API_Info" translated class name, NetWare Administrator obtains the class name associated with the translated class name. It then obtains the procedure pointer registered with that class name and sends the procedure, in this case snapinAPIObjProc, the CREATEOBJECTmessage.

In the CREATEOBJECTmessage, the objectName parameter contains the name context describing the user's container selection in NetWare Administrator. snapinAPIObjProc passes this context to the addAPIObjectroutine along with the p1 parameter. The snapin will set p1 to the name given to the new object by the user.

Note: Refer back to the addAPIObject routine source listed earlier in this article for the following discussion.

The first thing the addAPIObject routine does is display the create object dialog (Figure 21). This dialog is created and managed entirely by the snapin. Once the create object dialog is displayed, the user can enter the desired name for the new object.

When the user selects the Create button, control is returned to the addAPIObjroutine. Recall that the distName member of the global apiObj is intended to contain the distinguished name for the object being operated upon. The distNamemember of apiObj currently contains the object name just entered by the user during the create object dialog procedure (not shown). This value is put into the local distName variable providing the first part of the object's distinguished name.

Next a period is added to provide a delimiter between the object name and the object's context. Next, the context for the object that was sent to the snapin by NetWare Administrator with the CREATEOBJECTmessage is concatenated to distName to produce the fully distinguished name for the new object.

The distinguished name in the local distName variable is then installed in the distNamemember of the apiObjglobal

addAPIObject then creates a context. This context will provide a relative base which can be added to a partial name context and used to specify a location in which to instantiate an object. In this case, addAPIObject sets the name context value in the context to "[Root]." When the distinguished name in distName is added to the root context, the sum provides the needed location information.

As you will recall from the previous article, all NDS requests are performed with buffers. In addAPIObject, NWDSAllocBuf allocates a buffer called objectInfo to contain the request for a new object. The new buffer is initialized for a DSV_ADD_ENTRY operation. The buffer type item tells NDS how to parse the buffer once it is received.

Note: Although the API_Info class has four mandatory values, we only need to initialize three of them. The fourth value, CN, was specified as the "named by" attribute. Later, after the request buffer is prepared, it and the context specifying the location for the new object will be sent to the tree using the NWDSAddObject call. NDS will obtain the object's name from this context parameter and then automatically initialize that value when it creates the object.

Next, addAPIObject adds an attribute name item to specify to NDS that the data following the item is intended for the object class attribute. The APIInfoClass name is then installed as the value for the attribute.

This value will be referenced when a client searches for API_Info objects. All objects in the tree with this class name will be found and considered for selection as the optimal instance of the API_Info NLM on the tree.

Next, addAPIObject installs the initial values for the snapin's two editable attributes. Recall that these two attributes utilize the distinguished name syntax. They are set to "[Root]" because that is the only distinguished name that is universally acceptable to any tree.

NDS won't allow a distinguished name value not contained in the target tree to be installed into an attribute with a distinguished name syntax.

The completed request buffer is sent to NDS along with the context and the distinguished name string parameters in the NWDSAddObject call. It should be noted here that in some NDS calls, the context parameter is intended to establish the context for the user making the request so that NDS can check the user for the effective rights to perform the operation.

In other NDS calls, like NWDSAddObject, the context parameter is intended to establish a location for an operation (e.g., where to create a new object).

After the buffer is sent, the buffer and the context are no longer needed so they are freed. And since NetWare Administrator also needs to know what the object was named, the addAPIObject routine copies the distinguished name into the p1 parameter.

The Define Additional Properties and the Create Another Object buttons on the create object dialog (see Figure 21) are mutually exclusive selections. If the user selects Define Additional Properties, addAPIObjectreturns SHOWDETAILS to NetWare Administrator, causing it to display the multipage edit window for the object.

For most snapins, this dialog would also allow the user to enter values for optional attributes. But to keep things simple, the API_Info snapin only allows editing of the server and operator attributes.

Clicking the Create Another Object button causes the snapin to return a CREATEANOTHER message, which causes NetWare Administrator to send the snapin another CREATEOBJECT message, causing the snapin to redisplay the create object dialog and allowing the user to create another object.

After the object has been created--and if neither the CREATEANOTHERnor the SHOWDETAILS buttons have been clicked--addAPIObject returns a value indicating success to NetWare Administrator, causing it to update the display for the new object.

Note: Refer to the snapinAPIObjProc routine shown source listed earlier in this article for the following discussion.

The CLOSESNAPINmessage is sent to snapinAPIObjProc when the user quits NetWare Administrator. To handle the CLOSESNAPIN message, the API_Info snapin simply calls NWARemoveClassData to unregister the API_Info class information that was registered during the processing of the INITSNAPIN message. Finally, the snapin deletes the Windows icon objects that it created during the INITSNAPINmessage.

Using the Snapin to Edit an Object in the Tree

So far, we have discussed how to develop a snapin that would allow a user of the NetWare Administrator application to create an object with preinitialized attribute values. Now we will discuss how to enable the snapin to allow a user of the NetWare Administrator application to edit attribute values using the multipage edit window.

To display the multipage edit window for an object, the user can either double click on the object or select it and then select the details item under the Object menu. Figure 23 depicts how NetWare Administrator and the API_Info snapin interact to allow users to edit an object's attribute values.

Note: snapinAPIObjProc is the API_Info routine which corresponds to the "Object Proc" in the block diagram below.

Figure 23: The NetWare Administrator/Snapin Protocol for editing object attributes.

Note: Refer to Figure 23 and the snapin source code listed earlier in this article for the following discussion.

  1. The first message sent to the snapin after the user attempts to get details on an object is GETPAGECOUNT.Accompanying the GETPAGECOUNT message in the objectName parameter is a fully distinguished name for the object that the user wishes to edit.

  2. The snapin must respond to the GETPAGECOUNT message by reading the current values from the target object into its own global variables so they can be displayed in the multipage edit window. For the API_Info snapin to accomplish this, the GETPAGECOUNT message is parsed, and a routine called initObjectModifyis called.

    The purpose ofinitObjectModify is to initialize the global editable attribute description data structures so that they contain the current values from the target object.

    Recall that the global apiObj is composed of an array of two records to describe each editable attribute in the API_Info object and a distinguished name for the object being operated upon. The first line of initObjectModifysets the distName member of apiObjto the distinguished name sent to the snapin with the GETPAGECOUNT message.

    The GETPAGECOUNTmessage does more than just set the distName member of apiObjand return the number of pages in the multipage edit window. GETPAGECOUNT also causes the target object to be read to initialize the value members in the editable attribute data structures before they are displayed in the multipage edit window.

    Now that the distinguished name for the object is stored in the apiObj global, the snapin can read those attributes with the readAPIObjectData routine. The readAPIObjectData function allocates a context and sets it to [Root]. This context together with the distinguished name in apiObj. distName defines the location and identity of the object to be edited.

    Notice that readAPIObjectData has two buffers, one to hold the names of the attributes to be read and the other to hold the results of the read. First, the name buffer is allocated and initialized for a DSV_READ operation. Then the response buffer is allocated. The response buffer doesn't need to be initialized. NDS will do that automatically.

    Recall that the attrName members of the editable attribute data structures were initialized to the attribute names for each of the API_Info object's editable attributes. Both the APIInfo Server attribute name and the APIInfo Operator attribute names are installed into the name buffer.

    Applications must sometimes repeat an NDS request to read or scan information multiple times in order to have it completed. This is because, at a lower level, multiple NDS network transactions may be required to obtain the desired information.

    In these NDS calls, an iteration handle is used to manage the process state of the request. Before the operation loop, the iteration handle is initialized to NO_MORE_ITERATIONS. The operation is complete when NDS sets the iteration handle to NO_MORE_ITERATIONS, which is a minus one.

    If your code makes an NDS request that requires an iteration handle and you find what you are looking for before the iteration handle is set to NO_MORE_ITERATIONS, it is important that you terminate the iteration loop with the NWDSCloseIterationcall.

    NWDSCloseIteration releases the NDS resources that have been allocated to satisfy the request. The API_Info snapin needs to perform a complete read for both attributes, so the NWDSCloseIterationcall is not needed.

    NWDSRead is an iterative NDS request. Its first two parameters establish the name and location of the object to be read. The third parameter specifies that this request is to read attribute values.

    The fourth parameter references the NDS buffer containing the names of the attributes that we want to read. The fifth parameter is the iteration handle and the sixth is the buffer that was allocated to contain the attribute values that will be read.

    The NWDSGetAttrCount call looks in the response buffer to determine how many attribute values it contains. Then for each attribute, the name of the attribute is identified and the value of the attribute is stored in the appropriate global member.

    This goes on until both attributes have been read, at which time the iteration handle will equal NO_MORE_ITERATIONS. After the object has been read, the context and the buffers are freed and the routine terminates. After reading the current editable attribute values from the selected object, initObjectModify returns the number of pages in the snapin's multipage edit window, which in API_Info's case is two.

  3. NetWare Administrator will then send the snapin one REGISTERPAGE message for each page in the snapin's multipage edit window. The snapin must respond by sending NetWare Administrator information about the page, including a procedure pointer to the snapin routine that will manage user actions on the page's buttons and text controls.

    Accompanying the REGISTERPAGE message will be the distinguished name of the target object in objectName, the index for the page to be registered in p1, and a pointer to memory allocated by NetWare Administrator to contain the values from the page description data structure for that page.

    The registerAPIPage routine simply assigns the values from the appropriate data structure in the array into the memory allocated by NetWare Administrator. The first index sent by NetWare Administrator is a zero, which references the host server page.

    Previously, we described how these page description data structures were initialized. The dlgProcand resNammembers are worthy of special attention. dlgProc contains a pointer to the routine, which is to handle the user interface for the host server's page area (Figure 22), and resName is the name of the resource in the snapin's file which contains the resource definitions for all of the controls in the host server's page area.

    After making the assignment, registerAPIPage returns a value indicating a successful operation to NetWare Administrator. NetWare Administrator responds by sending the snapin a REGISTERPAGEmessage for the second page.

    This time NetWare Administrator indexes the page description data structure for the operator attribute. After making the assignment,registerAPIPage returns a value indicating a successful operation to NetWare Administrator.

  4. After registering each page's information, NetWare Administrator must display the multipage edit window and register the current page's dialog procedure with Windows. The dialog procedure will then receive messages directly from Windows when user actions occur within the page's area in the multipage edit window.

    In order for NetWare Administrator to obtain the information needed to draw the controls in the page area, it must reference the page's resName returned by the snapin. NetWare Administrator references the pageTitle members from both data structures to get the page and button titles.

    All user actions within the page area result in messages which are sent to the procedure specified in the page's dlgProc member. The page dialog procedure is much like the snapin's object procedure in that there is a message parameter and two other parameters which can carry additional information needed to process the message.

    A difference is that most of the messages sent to the page dialog procedure come from the Windows operating system instead of NetWare Administrator.

    When the multipage edit window is first drawn, an INIT_DIALOG message is sent for each registered page. Recall that during the GETPAGECOUNT message from NetWare Administrator, the snapin read the editable attribute values from the specified object.

    SetDlgItemText is a Windows routine that puts the value read from the object's host server attribute into the distinguished name text control of the page area. This value is "[Root]" because this is a new object.

    The modify flag in the editable attribute's global structure is set to FALSE. Later, if the user changes contents of the distinguished name text control in the attribute's page area, the modifyflag will be set to TRUE.

    The dialog proc returns TRUE to indicate a successful dialog initialization.

  5. If a user-action occurs in the NetWare Administrator window but outside of the snapin's page area, Windows will send a user-action message to NetWare Administrator. If the action occurs inside of the page area, Windows will send a user-action message to the current page's dialog procedure.

    Note: Refer to the modifySrvrDlgProc routine's source code listed earlier in this article for the following discussion.

    For example, when the user clicks the Browse button (inside of the page area) to look for an appropriate host server for the API_Info NLM, the host server page area's dialog procedure is sent a Windows message with the resource identifier for the Browse button in the lower half of thewParmparameter.

    The parseDistinguishedName call is a snapin defined routine that simply obtains the name context from the fully distinguished name in the global and puts the name context into tempString. This context string is then passed in the call to NWALaunchDSFlatBrowser to launch NetWare Administrator's object browser.

    Accompanying the context string is a pointer to the snapin-defined routine hSrvrFlatBrowserLaunchProc(the code for hSrvrFlatBrowserLaunchProc is not shown in the code listed in this article). hSrvrFlatBrowserLaunchProc manages the snapin's responsibilities during object browsing for a host server object.

    The objects displayed in the flat browser can be filtered. In this case, the hSrvrFlatBrowserLaunchProc callback has specified that the browser display only server objects.

    After the user selects a server object, hSrvrFlatBrowserLaunchProc sets the distinguished name text control in the page area to the distinguished name of the selected server object.

    When a page's contents have been changed, NetWare Administrator will fill the tab on the page's button. Notice that the tab on the page in Figure 22 is clear and also that the OK button outside of the page area is disabled. This state would mean that NetWare Administrator doesn't yet know there is a new value available for the attribute.

    However, when Windows knows that the value in the text control has changed, it sends the page's dialog procedure a WM_COMMAND Window's message with the text control's resource identifier in the lower half of the wParmparameter.

    The upper half of the wParm parameter specifies the reason for the message. In this case, the message is intended to notify the procedure that the value in the text control has changed.

  6. If the user changes an attribute value in one of the page's text controls, the dialog procedure must send NetWare Administrator a SETPAGEMODIFY message to tell NetWare Administrator that when the user clicks the OK button in the multipage edit window, it should send the snapin a MODIFYmessage. As you will see, the MODIFY message to the snapin is the message that actually causes the snapin to write the new values out to the target object.

    The supposedly new value is obtained from the text control and compared with the current value in the object. If they are different, proving that the value is truly new, the modify flag for the attribute is set and the SETPAGEMODIFY message is sent to NetWare Administrator, telling it that a new value has been entered.

    NetWare Administrator responds to the SETPAGEMODIFY message from the snapin by filling in the page's button tab and enabling the OK button so that the user can accept the change if desired.

    When the user clicks the Load NLM button (Figure 22), the dialog procedure is sent a WM_COMMAND Window's message with the resource identifier for the Load NLM button in the lower half of the wParm parameter. The procedure responds by obtaining the distinguished name for the server from the text control and sending it to the snapin defined routine loadAPIInfoNLM(not shown).

    Note: Refer to the modifyOprtrDlgProc routine's source code listed earlier in this article for the following discussion.

    When the user clicks the Operator page button, NetWare Administrator must reference the second page description data structure to display the Operator page (not shown). The Windows messages for the Operator page are processed in a similar fashion as they were in the host server page.

    If the operator page's dialog procedure is sent the INIT_DIALOG message, the distinguished name text control is set to the current value of the target object's Operator attribute and the modify flag is cleared. If the user browses, the operator page's flat browser lists only the available user objects.

    Again, if the text control value is changed during browsing, the procedure sends NetWare Administrator a SETPAGEMODIFYmessage.

  7. When NetWare Administrator receives the user action message from Windows that the user has clicked the OK button to close the multipage edit window, NetWare Administrator checks to see if it has received a SETPAGEMODIFY message from the snapin. If it has, it sends the snapin a MODIFYmessage.

    This is the only message handled by this procedure that does not come from Windows. The procedure processes the message by reading the object's distinguished name from the page's text control and putting it into the editable attribute's newValue global for temporary storage. Incidentally, all registered pages receive this message, so if the operator page were currently displayed, the host server's distinguished name would be saved as well.

    If the snapin has sent NetWare Administrator at least one SETPAGEMODIFY message, when the user clicks the multipage edit window's OK button, NetWare Administrator follows up by sending the snapin's object procedure, snapinAPIObjProc, a MODIFY message accompanied by the distinguished name for the target object in the objectName parameter.

  8. The snapinAPIObjProcroutine responds to the MODIFY message by calling the snapin defined modifyAPIObject routine. This routine will write the new values to the target object.

    Note: Refer to the modifyAPIObject routine's source code listed earlier in this article for the following discussion.

    First, modifyAPIObject creates a root context, which along with the distinguished name parameter, fully defines the location and name of the target object. Next modifyAPIObjectallocates an NDS buffer and initializes it for a MODIFY_ENTRYoperation.

    The host server's modify flag was set in its page dialog procedure, so this NWDSPutChange call specifies that a value in the host server attribute is to be deleted.

    Recall from earlier in this article that some attributes can have multiple values. The following NWDSPutAttrVal call puts information in the buffer to indicate which value of the possible multiple values is to be removed.

    You need to make the NWDSPutAttrVal call even though API_Info's editable attributes are defined as single valued. The next two calls request an add value change specifying the host server distinguished name obtained from the multipage edit window as the new value.

    The operator attribute change information put in the buffer is very similar to the already installed host server attribute change information. The buffer is sent to the NDS tree with the NWDSModifyObjectcall. modifyAPIObject then frees the context and buffer and returns a value indicating success to NetWare Administrator. The next time the user gets details on this object, the new editable attribute values would be displayed.

    Note: Refer to the snapinAPIObjProc routine's source code listed earlier in this article for the following discussion.

    The remaining three snapin messages are relatively minor when compared to the messages that we have just discussed. The RENAME message is sent to the snapin when a user attempts to rename an object using NetWare Administrator. Just return the DO_DEFAULT message and NetWare Administrator will rename the object for you.

    The QUERYDELETE message is sent to the snapin when a user attempts to delete an object using NetWare Administrator. If your object is referenced by other objects, this gives your snapin a chance to resolve those relationships. The API_Info snapin returns the SUCCESS message so that NetWare Administrator will delete the object.

    The QUERYMOVE message is sent to the snapin when a user attempts to move an object into a different container. Again, if your object is referenced by other objects, this gives your snapin a chance to resolve those relationships. The API_Info snapin again returns the SUCCESS message so that NetWare Administrator will relocate the object in the tree.


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© 2014 Novell