Building eDirectory-Enabled Applications using Delphi: Low-Level

Articles and Tips: article

This is the third and last in a series of AppNotes on eDirectory programming with Delphi. The first article in the November 2001 issue introduced the two method- ologies for eDirectory access: ActiveX controls vs. low-level calls. The second article in the December 2001 issue tooke a closer look at the ActiveX approach and reviewed the specific benefits of that approach. This third article will review the more complex but often more efficient low-level technique. It will also introduce a Delphi helper unit that allows you to combine the speed of low-level programming with the comfort of high-level syntax.

Introduction

Low-level programming for NetWare with Delphi uses direct calls into the NetWare Client APIs. The Novell NetWare client implements this client interface in the form of Dynamic Link Libraries (DLLs). Practically all NetWare Windows applications (with NWAdmin.exe being a prime example) call into these DLLs, which is significantly faster than calling into an intermediate OLE layer (like with the ActiveX controls).

One drawback of this approach is that you have to choose between literally hundreds of available APIs; but the main disadvantage is that you typically must call a sequence of multiple APIs to perform basic functions such as locating an NDS object or reading attributes. You have to familiarize yourself with topics like buffer handling or pointer operations in NDS APIs.

If this sounds complicated, that's because it often is. But in this AppNote I will try to make things easy, do one step at a time, and take you by the hand on your way. You may find that the first steps are not as difficult as you may have thought-and you may be surprised by the snappy performance of your applications. Using the low-level approach avoids the entire overhead that must be implemented in ActiveX controls. You don't need to talk to the OLE layers in Windows, and your application requests no more information from the network than it actually needs.

The first part of this AppNote introduces the concepts, the installation, and shows you how to perform some simple tasks with the low-level approach. Later, I will introduce a Delphi helper unit that can make your programming life much easier.

The sample code for the examples in this AppNote can be downloaded from the Web at http://developer.novell.com/research/appnotes/download.htm.

Architecture

You don't need much to call a low-level API from a DLL from Delphi: you'll need to tell Delphi where to find the API (implementation), and what the format of the expected parameters and return values will be (interface). All of this is available as a component of the Novell Developer's Kit (NDK) in the "Novell Libraries for Delphi". NDK components can be downloaded from the Web at http://developer.novell.com/ndk/.

As an example of this approach, look at the NetWare function NWDSWhoAmI(). The implementation takes place in the NetWin32.IMP file, where it says:

function NWDSWhoAmI; StdCall; external 'netwin32.dll' index 1093;

This basically tells Delphi that the implementation of this API can be found in the external library "NetWin32.DLL" at position 1093 (you can guess from this number that there are a handful of other APIs in this DLL).

The additional information that Delphi needs-the exact calling conventions- can be found in the NWDSDSA.INC file in the Novell Libraries for Delphi, where it says:

From these lines, Delphi will know that this API needs two parameters and returns a value of type NWDSCCODE.

Important Concepts

This section introduces some important concepts to understand when dealing with NetWare APIs.

New NetWare Type Declarations

The variable types shown in the function declaration above may seem cryptic to you, as Novell has introduced a whole set of new data types for the NetWare APIs. You will see plenty of similar types in the NDK samples and documenta- tion, so it's important to understand the logic behind these new data type declarations.

The names typically are less cryptic once you understand the underlying logic: the letter "n" stands for "Novell", "p" for "pointer", "int" for "Integer", "str" for string, "u" for "unsigned", and the numbers for the bit-width of the data type.

Knowing this, you can quickly see that, for example, "nstr8" stands for a Novell 8-bit string (that's a single-byte character), "pnstr8" stands for a pointer to a 8-bit character (like a pChar). Also, "pnuint32" is a pointer to an unsigned 32-bit integer, and "ppnuint32" is a pointer to that pointer.

Why did Novell declare its own data types instead of using the predefined ones in Delphi? The reason is so that a variable of type nuint32 will always refer to a 32-bit integer. Who knows if Borland's LongInt will be 64 bits-or even 128 bits -in a future Delphi release? By defining specific NetWare types, Novell has the required control over the data types used for the DLL calls and thereby becomes less compiler dependent.

Many other data types serve specific purposes and are used to save return codes, handles, buffers, and so on. These aren't important for understanding this AppNote, but if you're interested you can find a complete list of declarations in the INC files of the Novell Libraries for Delphi.

Connection References and Connection Handles

Your client maintains an internal table with one connection reference for each server that you are connected to. If you are connected to five file servers, this table has five entries. Scanning your connection references is a quick way to identify the servers that you are connected to. These connection references are shared between your applications.

Connection references are variables of type nuint32.

Connection handles are maintained by and within your application. I often compare these to file handles: they are used as handles to a resource (server or file) to make access to the resource easier. Frequently you have one handle per resource, but if necessary, your application can have multiple handles to the same resource. Handles are not shared between applications. A handle defined in one application is invisible to other applications.Connection handles are variables of type NWCONN_HANDLE.

The interaction between references and handles is as follows: If you open a connection handle to a new server that is not yet in your connection reference list, the NetWare client will automatically add a new connection reference. On the other hand, if your application closes a connection handle to a server, the connection reference will be removed, provided no other applications are using that server.

Context Handles

While a connection handle points to a file server, the context handle points to a specific location in the NDS tree. Again, you may have one or multiple context handles pointing to the same or different locations in one or more trees.

Besides identifying the context location, a context handle will also determine other defaults of your interaction with eDirectory, such as whether you want to used canonicalized names, double-byte strings, typeless names, and more. See the API documentation of NWDSSetContext() for details.

Return Codes

The NDK documentation lists the type of return code for each API. Typically, return codes are of type NWRCODE, NWCCODE, or NWDSCCODE. A return code of zero (N_SUCCESS) indicates success; other codes indicate failure. Most error codes are declared as constants in NWERROR.INC and NWDSERR.INC, which may be helpful in analyzing problems. You can also convert error codes into error strings with the help of NWErr32.DLL by calling U_Nwstrerror() from the D_NW helper unit.

Before We Get Started

Personally, I prefer to learn by doing. What about you? Well, in a moment we'll get started and take the first small steps into unknown terrain. But first, we need to install the NetWare Libraries for Delphi and go over a few more basic concepts.

Installing the NetWare Libraries for Delphi

This installation consists of two quick steps: downloading the libraries and informing Delphi of the libraries.

Download. You can download the libraries from Novell's DeveloperNet Web site at http://developer.novell.com/ndk/delphilib.htm. If you are not yet a registered member of DeveloperNet, choose the free membership for now.

Execute the downloaded DELPHILIB.EXE file to copy the Delphi header files and a small Delphi sample to your local hard drive.

Inform Delphi. Delphi needs to know what files to include in its search for external libraries. To inform Delphi of the libraries you just copied over, start a new project and select Project | Options. Select the Directories/ Conditionals tab and in the "Search Path" field add "C:\Novell\Libraries\Delphi\Src". To avoid having to re-enter this for every future NetWare project, click the "Default" checkbox.

A Few More Concepts to Understand

With the libraries installed we could start coding, but there are a few more important concepts to go over before we do.

Get Some Help. You will find the complete HTML-based documentation for the low-level APIs on the Web at http://developer.novell.com/ndk/doc.htm. It is combined documentation for C and Pascal/Delphi that you can download or read online. You'll find the information organized by topic group.

In this AppNote we will mainly discuss APIs from the NDK groups "NDS Libraries for C" and "NLM and NetWare Libraries for C". Depending on your development project, you may also want to look into other NDK components such as "Print Libraries for C" and "Novell Single Sign-on for C". Even though the titles refer to "C", these topics include Delphi information as well.

Once you have included the Delphi libraries in your project options and "uses" clauses, Delphi will also provide you with the code completion features for the Novell APIs.

Include the Units. If you have installed the Delphi libraries as instructed above, then you have followed my recommendation to add the path to these libraries as Delphi project defaults. In order to use NetWare APIs in your application, you'll also need to include the Delphi units in your project.

Typically, you'll need to add one or more of these Delphi units to your program's "uses" clause:

Initialize. One important thing to mention is that every application needs to initialize the NetWare functions and Unicode tables. This is done with a call to the API NWCallsInit() before any other NetWare API is called; the call takes two nil pointers as parameters.

If you forget this initialization, you may get a Windows GPF (General Protection Fault) error when doing the first NetWare call. Typically, you add the call to this API in the FormCreate() event and show a meaningful error message if the API fails. Be sure to check the return code of this call: if it fails, no other NetWare API will work. The typical reason for failure of this API is a missing or bad installation of the Novell NetWare client. Yes, you do need this client to call the APIs that we are talking about here.

The Testing Environment. To keep the projects for our first tasks as simple as possible, we'll use a standard project as our template. This project consists of a form with one button and one list box. Later projects will also need two edit boxes, while some samples will need one or two additional combo boxes instead.

For the sake of simplicity, I will keep the default names for components and events (Listbox1, FormCreate, and Button1Click). Please add the respective events to your project components. Your testing project should look similar to the one shown in Figure 1.

Basic form in design mode.

For ease of demonstration, we will use some standard events and variables in all our projects. The common code should resemble what is displayed in the following template:

The first lines some declare global variables. We will use the variable cCode to store return codes, and the variable ctx to store the NDS context handle.

In the FormCreate event, we initialize the libraries with NWCallsInit, then create a context handle with NWDSCreateContextHandle. We do not set any specific context flags; instead, we will use our default tree and context, and the default naming conventions.

The FormClose event will free the context if one has been created.

Common eDirectory Tasks

Now we are ready to look at how to perform some common eDirectory tasks with low-level API calls from Delphi. For the first couple of projects, I'll start with the step-by-step approach and look into individual eDirectory APIs. I will then introduce a helper unit that will make the work much easier.

As in the previous AppNotes in this series, no sophisticated error handling is implemented so you can focus on the essentials. In practice, however, you should make sure that NDS return codes are checked and taken care of properly, before proceeding with your program flow.

Project 1: Who Am I

Obtaining information about your own account is an easy task: only a single NDS API is involved.

The API NWDSWhoAmI expects the context handle as an input, and a pointer to the target buffer for the output. The code simply declares a character array and passes the address of this array for the result. On completion of the call, the pointer to the character array is converted to a string and displayed in the listbox.

Project 2: Connected Servers and Trees

If you need to identify the names and trees that your client station is currently connected to, you can use the connection APIs to scan your connection references and retrieve the server and tree name for each of the references. This can be done with two new APIs: NWCCScanConnRefs and NWCCGetConnRefInfo.

NWCCScanConnRefs is called iteratively to loop through the connection reference table. For each identified reference, you retrieve some information, namely the server name and the tree name.

Project 3: Set Tree and Context

This sample project shows how to use the context flags to get and set context information. Here, you'll use the flags to read and modify the current tree and context settings for a given context handle.

In this example two new APIs are introduced: NWDSGetContext to retrieve the current context settings, and NWDSSetContext to change them. Both APIs expect a pointer to a character array for input or output, and in both cases a pChar typecast is used to provide this.

Note:
When changing trees, you should typically also change the context to avoid working with an invalid container name. If in doubt, consider using "[Root]"as a context.

Project 4: Browse a Container

Browsing objects in a container is a task that requires a handful of new NDS APIs. Here are the required steps:

Allocate memory for the output buffer by calling NWDSAllocBuf.

Set the iteration handle to NO_MORE_ITERATIONS.

Call NWDSList.

Determine the number of subordinate objects in the output buffer by calling NWDSGetObjectCount.

Call NWDSGetObjectName for each subordinate object in the output buffer.

If the iteration handle is not equal to NO_MORE_ITERATIONS, loop to Step 3. Otherwise, go to Step 7.

Free the output buffer by calling NWDSFreeBuf.

As you can see, each call to NWDSList will retrieve a whole set of NDS objects into a buffer, and the subsequent calls reads the objects from the local buffer one by one. This keeps the network traffic to the bare minimum.

The following Delphi sample reads the objects from the current container using the steps outlined above.

To identify the class of the retrieved objects, you may get the information from the objInfo variable.

Project 5: Browse a Container Revisited - Introducing the Helper Unit

As you can see from the examples above, it is not difficult to access NDS with low-level APIs, even though some of the pointer operations, type conversions, and buffer operations may be challenging at first. From experience, however, you will know that there are a couple of standard tasks that likely will be implemented in most applications.

During my own work with the Delphi and the NDS APIs, I have successively created a helper unit that contains such standard tasks, as well as some less frequently-used operations. Currently, my helper unit has more than 100 typical NetWare functions that can easily be integrated into your application or adapted to your specific needs. To give you an idea of what is this helper unit can do for you, the following table lists just a small fraction of the available functions.

U_NWDateTimeToStr

U_DSCreateClass

U_NWPathToUNCPath

U_DSSetContextFlag

U_FSCopyFile

U_DSGetNDSObjectID

U_MapAvailableDrive

U_DSGetObjectName

U_GetDefaultServer

U_DSGetParentObject

U_GetServerNameFromConnRef

U_DSGetPartitionRoot

U_GetUserNameFromConnRef

U_DSWhoAmI

U_ListAddNLMs

U_NWstrerror

U_ListAddServers

U_DSChangeCurrentTreeAndContext

U_ListAddObjects

U_DoSomethingForEachContainer2

U_ListAddMembers

U_DoSomethingForEachObject2

U_GetBinderyObjectName

U_DSReadAvailableClasses

U_DSListAddTrees

U_DSReadAvailableAttributes

U_DSListAddObjects

U_DSReadStreamAsString

U_DSChangeAttr

U_DSGetAttributeAsString

As you can see, all helper APIs start with the “U_” prefix. They come in full source code so you can customize their behavior if needed. Some documentation for these helper functions can be found in the source code of D_NW.pas. For most of the functions, you will find sample code in Novell's DeveloperNet sample code area at http://developer.novell.com/support/sample.htm.

To see how these helper APIs can make your life easier, let's combine some of the tasks we worked on above and see how you could achieve the same things with the helper functions. Add the NetWare helper unit D_NW.pas and the generic helper unit D_Util.pas to your project and try this:

The following projects will strongly utilize functions from this helper unit to help you become more familiar with its features.

Project 6: Read the Schema

This project shows how to obtain schema information from eDirectory. The purpose of this application is to list the available classes, then show the available attributes for the selected class. To allow an easy selection of the class, add a new ComboBox component to the Delphi form.

When the button is clicked, the program will read all available classes from the schema and put them into the ComboBox. Then, if you select one of the classes from the list, all eDirectory attributes for that class will be listed. The function U_DSReadAttributes allows for some customization. The number indicates what attribute type you want to see (mandatory, naming, optional attributes, and so on), and the Boolean operator indicates whether you want the attribute's type displayed.

Project 7: Read an Object's Attributes

This example is supposed to show how to read attribute values for a given object. To allow for some flexibility while keeping the code simple, we'll create an application that has two combo boxes. Add the same OnChange event to both combo boxes.

The first combo box contains the users in the current container; the second combo box lists potential user attributes.

If the button is clicked, the sample code fills the combo boxes with the available users and attribute names. If you select a user and an attribute, the user's attribute value will be displayed in the listbox.

Project 8: Display More Meaningful Error Messages

While we did not do extensive error checking in the previous projects, I'd like to conclude with an easy method to display more meaningful error messages. The error codes NetWare returns may be meaningful to some of you, but probably not to the majority of users and programmers. For example, most people would find it more helpful, instead of getting a message such as "Error FDA5", to receive a more descriptive message such as "Error FDA5: NO SUCH ATTRIBUTE".

The function U_Nwstrerror allows you to retrieve the error message string for a given return code. Here is a little code snippet that shows you how to use the function:

U_Nwstrerror uses NWErr32.DLL, which can be downloaded at http://devel- oper.novell.com/support/sample/tids/nwerrdll/nwerrdll.exe. This download also contains sample code for Delphi, Visual Basic, and C. If the DLL is not present on the client machine, the function simply returns the hex error code.

Some Concluding Remarks About D_NW.pas

The original purpose of this helper unit was to make my life easier, but I am sharing it because I think you might find it useful too. This helper unit, together with another helper unit named D_Utils.pas, is included with many of the Delphi samples on http://developer.novell.com/support/sample.htm. You will find that no two of these are the same. Every month, new functions are added or existing functions improved. To take advantage of such improvements, you might want to watch the sample code area for more recent versions.

This helper unit is sample code, not a Novell product, and as such it is not officially supported. However, you can post questions and comments on the Developer newsgroup where SysOps and myself monitor questions about the Novell Libraries for Delphi. You will find a link to this newsgroup forum at http://developer.novell.com/ndk/delphilib.htm.

Conclusion

After this final AppNote in the series on application development with Delphi, you should have a good idea of how to use Delphi to create eDirectory-enabled applications.

We have covered two approaches, ActiveX and low-level calls, which have their distinctive pros and cons. If you follow the Rapid Application Development (RAD) approach and use off-the-shelf ActiveX components, a lot of the underlying complexity will be hidden from you. The more time-consuming but more flexible and powerful low-level approach with direct calls into the client DLLs will provide you with better resource utilization and higher speed. Using the latter approach with some available helper units may give you the best of both worlds.

But after all, the main purpose of this AppNote series was to introduce both concepts and to help you make the right decision as to which one to use. The choice is up to you!

* Originally published in Novell AppNotes

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.