Developing C++ NLMs

NetWare Loadable Modules (NLMs) are 32-bit utilities that dynamically link into NetWare. Dale examines the complexity of writing NLMs in C++, then presents DSBROWSE, a utility that lets you view (or "walk") the NetWare Directory Services tree.


August 01, 1995
URL:http://www.drdobbs.com/cpp/developing-c-nlms/184409613

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

AUG95: Developing C++ NLMs

Developing C++ NLMs

Walking the NDS tree--in C++

W. Dale Cave

Dale, a software engineer for Novell, is currently working on NetWare Directory Services. He can be contacted at [email protected].


Netware Loadable Modules (NLMs) are 32-bit utilities that, when run on a NetWare server, are dynamically linked into NetWare, thereby extending the network operating system. Many NetWare features--disk drivers, LAN drivers, and libraries such as CLIB, DSAPI, and NWSNUT--are NLMs. Even NetWare Directory Services (NDS) is an NLM, DS.NLM. These NLMs take advantage of NetWare's multitasking, multithreaded, and resource-management capabilities. DSBROWSE, the utility I present here, allows you to view (or "walk") the NetWare Directory Services tree. What's atypical about it is that it's written in C++.

NLM internals are generally straightforward and have been discussed in many books and magazines, including Dr. Dobb's Journal (see "Implementing NLM-based Client/Server Architectures," by Michael Day, October 1992). Until now, most NLM development has been done in C. C++ introduces more complexity. Consequently, instead of describing how the DSBROWSE NLM works, I'll focus on the tools and process of writing C++-based NLMs. DSBROWSE is provided in both source and binary form; see "Availability," page 3.

Looking at the Workbench

For starters, NLM development is best done with two computers: a client workstation, for editing, building, and remote debugging; and a NetWare server, for running and testing the NLM.

The C/C++ compiler of choice for most NLM developers is Watcom C/C++ 10.0. Watcom introduced C++ support in Version 9.5 and has updated it twice with patch A and patch B. I used Version 10, patch A for developing DSBROWSE. This compiler supports many features expected from a C++ compiler, including templates, exception handling, I/O streams, and source-level debugging. You can get patch A directly from Watcom by downloading the files c_a.zip and ch_a.zip files from file area 17, "Watcom C/C++ 10.0 Problems and Fixes" forum on Watcom's BBS. The files are also available via anonymous ftp from ftp.watcom.on.ca in directory /pub/bbs/lang_v10.0/c.

It is necessary to install the Watcom compiler first, then the Novell NLM SDK. Next, apply patch A (unless you are installing from an already patched version). If the compiler is already installed, TECHINFO.EXE located in the WATCOM\BINB directory will analyze your development environment and display critical files and the level of patch applied to them.

Although Watcom claims to include the Novell NLM SDK 4.0 in its package, Novell's NLM SDK is still a necessity. In particular, the CD-ROM version of the Novell SDK provides documentation and messaging tools required for using the NetWare console text-based interface standard (NWSNUT). I developed DSBROWSE using the NetWare NLM SDK 3.5 (the current version is 4.1).

Although I created DSBROWSE on an Intel-based PC running Novell DOS 7, DOS 3.3 or higher should work. You also need an 80386 (or higher) PC with at least eight MB of memory. My installation for NLM development (including Watcom and Novell tools) consumes 45 MB of disk space, but other installations may vary. The NetWare server used for testing the NLM should be dedicated for testing. A production server should not be used.

Finally, C++ NLMs are supported for the NetWare 3.x and 4.x environments. However, DSBROWSE will only run on NetWare 4.x because it uses NDS.

Client Configuration

After the Watcom and Novell tools have been installed, check your environment variables and DOS path. If Watcom is installed to c:\watcom, your environment should have two Watcom-related variables: include=c:\watcom\novh;c:\watcom\h and watcom=c:\watcom. The order of include locations is significant. It is important to place the Novell header-file area (c:\watcom\novh) before the Watcom header-file area (c:\watcom\h) to ensure the proper header-file search order. It is also required to have the c:\watcom\bin, c:\watcom\ binb, and c:\novsdk\msgtools directories in your path or have them mapped as search drives. The first two directories contain Watcom executables, and both the bin and binb directories are necessary. The Novell NLM enabling (or "messaging") tools are used for internationalization or enabling. Although message enabling is optional for developers and DSBROWSE is not completely enabled, DSBROWSE.CPP (Listing One) shows one way of using message strings. (DSBROWSE.H is available electronically.) DSBROWSE's makefile, MAKEFILE (also available electronically), demonstrates the management of the message database.

Server Configuration

No special server "set" parameters are required for debugging NLMs. You simply copy the Watcom debugger to a directory in the server's search path, usually the SYS:\SYSTEM directory. The Watcom debugger filename varies depending on the NetWare version and other debugging options. Watcom NLM debugging will be discussed in more detail later.

To source-level debug an NLM, you need to load the Watcom NLM debugger on the server. In MAKEFILE, a copy dsbrowse.nlm n:\system is executed after a successful NLM build to place DSBROWSE .NLM in the test server's SYS:\SYSTEM directory. Note that drive n: must be previously mapped to the test server's SYS volume.

Compile

DSBROWSE is compiled by Watcom's WPP386.EXE. Although WCL386.EXE is available to both compile and link, I have chosen to use the separate compiler and linker utilities for demonstration purposes.

The command to build DSBROWSE is: wpp386 dsbrowse.cpp /3s /d2 /xs /bt=netware.

Link

WLINK.EXE is Watcom's linker. The DSBROWSE project has a link file named DSBROWSE.LNK (available electronically), which simply has a list of linker commands. The command to link DSBROWSE with its link file is: wlink @dsbrowse.lnk.

Referring to the listing of DSBROWSE.LNK, some important options must be included. The format statement form nov nlm... tells the linker to generate a Novell NLM. debug all and debug novell tells it to include all debug information, including source-level debugging information. Both of these debug statements are required to source-level debug the NLM.

Since DSBROWSE is partially enabled with messaging, option messages=dsbrowse.msg will include the default English messages found in the DSBROWSE file MESSAGES.H (available electronically). In addition to the import and module state- ments, which indicate where external references should be searched and which corresponding NLMs to autoload, the library path should be searched as indicated: libpath c:\watcom\lib386\netware;c:\watcom\lib386. (The Novell NetWare library directory should be searched before the Watcom library directory.)

Debug

The compiler and linker options previously mentioned will build a debug version of DSBROWSE. To better understand what you need to do to source-level debug an NLM, I'll briefly discuss Watcom's debugging architecture.

Watcom only supports remote debugging, so the machine running your application must be controlled by a separate machine, which can be connected by a parallel, serial, or SPX connection.

Watcom's server debugger, Watcom Novell Debugger v2.5, is an NLM that runs on the server and relays requests and information to and from the client debugging workstation; see Figure 1. Watcom supplies six versions of its debug NLM to allow for each combination of parallel (par), serial (ser), and SPX (nov) transport type to each major NetWare version (3.x and 4.x). Because I debug DSBROWSE on a NetWare 4.x server and through an SPX connection, I load NOVSERV4.NLM (refer to Watcom's documentation for more details).

The server console command to load Watcom Novell Debugger v2.5 is: load novserv4 nvi, where nvi is the NetWare Service Advertising Protocol (SAP) name. This is an arbitrary name posted on the network that the client debugging workstation will search. If no SAP name is given, the default novlink is used.

Once the server debugger is loaded (there is no need to load DSBROWSE.NLM), the client needs to be set up. Watcom's client debugger, WD.EXE v4.0, is executed at the DOS command line by: wd /vga50 -trap=nov;nvi, where /vga50 is optional and puts the video in a 50-column mode if the hardware supports it. The -trap=nov identifies the transport type just like on the server side. Since I'll use the network SPX connection to link the two machines, nov specifies that request.

Since I am using a network SPX connection, the client debugger needs to find the debug server on the network. It does this by searching SAP for the name nvi, which is specified on the server. Once again, if ;nvi is omitted, the default SAP name searched is novlink.

When the client debugger is loaded and finds the debug server, the server debugger will automatically load DSBROWSE.NLM. The client debugger will then display DSBROWSE.CPP and highlight the first executable line of code.

DSBROWSE

No login capability is included when you run DSBROWSE.NLM. NetWare Directory Services do not require you to login to "browse" the tree. Although this is the default NDS behavior, it is possible for browse rights to be revoked at specific locations in the tree by users of proper authority. In this case, a login ability might be useful, and it would be a good enhancement to DSBROWSE.

Be aware that no unload procedure is included in DSBROWSE. Every production NLM should register an unload function that will free resources when the NLM is unloaded from the console.

MAKEFILE will build DSBROWSE and will work with Watcom's WMAKE.EXE. The environment variables and path (or search drives) must be set up prior to running the make. Assuming all DSBROWSE files are in the default directory, the command to build DSBROWSE is wmake. WMAKE.EXE will search the default make filename of MAKEFILE.

Concerns

My client workstation consistently crashes after a debugging session. Although irritating, source-level debugging DSBROWSE has been effective and worth the nuisance.

C++ comes with its peculiarities, one of which is name mangling. Name mangling supports function overloading (functions with the same name). It's the naming of functions that include an encoding of the function's number of arguments and their types. This can be a problem.

With DSBROWSE, for instance, if a variable is to be made public in the NetWare environment, the variable sym-bol will have to be exported. DSBROWSE.CPP shows how two variables (exported in DSBROWSE.LNK) are handled. The first symbol ExportSymbolMangled actually becomes W?ExportSymbolMangled$ni because of name mangling. If ExportSymbolMangled is exported, the linker cannot resolve it. But the mangled name, W?ExportSymbolMangled$ni, can be resolved.

Another variable, ExportSymbolUnMangled, is included with the extern "C" linkage. This forces a C naming convention on the variable, which will prevent it from being mangled. Then it can be exported as is. Note that the C linkage is not available for C++ constructs. Although a class can be exported with the __export declaration, the Novell Internal Debugger will not recognize names with parentheses in them. This is a problem because all mangled methods have parentheses.

The last concerns are not obvious. The first is the use of Watcom's prelude object module __WATCOM_Prelude in place of Novell's _Prelude. The second is that of CLIB3S.LIB, Watcom's static-library version of Novell's CLIB.NLM. To build C++ NLMs, you need to use this static library. So even though you think you might be getting some functions from Novell's CLIB.NLM, you could be getting them from Watcom's CLIB version. These replacements may cause difficulty in obtaining technical support and identifying which product has the problem.

Summary

The tools exist today to develop C++ NLMs. Because there are some caveats, a C++ NLM project might require some research. The readme.log for the compiler is a good place to start. Watcom technical support can be contacted for product and development questions, while Novell can be reached for SDK and NetWare licensing assistance. Whatever you decide, developing C++ NLMs can be fun and Watcom's source-level debugging capabilities make NLM debugging easy and effective.

For More Information

Novell Inc.

Developer Relations

800-733-9673

ftp.novell.com

http://www.novell.com

Watcom Inc.

415 Phillip Street

Waterloo, ON

Canada N2L 3X2

519-886-3700

BBS: 519-884-2100

ftp.watcom.on.ca

Figure 1: Watcom remote-debugging architecture.

Listing One

/*  DSBROWSE.CPP -- Author: W. Dale Cave. DSBROWSE is a NetWare Directory 
Services tree browser. It will display and allow traversal of the Directory 
tree. C++ source code for browsing functionality. Unloading this NLM from the 
console with "unload" command will report resources freed by OS because the 
NLM did not free them. An exit procedure would need to be added to release 
these resources and prevent the messages. */
#include <iostream.h>
#include <nwsnut.h>
#include <advanced.h>
#include <nwenvrn.h>
#include <process.h>
#include <conio.h>
#include <nwdsapi.h>
char  **messageTable;
#include "messages.h"
#include "dsbrowse.h"
NUTInfo   *handle;
NWDSCCODE  nwCode;
int        CLIBScreenID;                                         
int        (*defaultCompareFunction)(LIST *el1, LIST *el2);
/* "ExportedSymbolMangled" and "ExportedSymbolUnMangled" are included only for
examples of exporting symbols in the C++ name-mangling environment. See
DSBROWSE.LNK for the export statement syntax. This symbol will be mangled 
in the C++ environment. With debug information on, a scan of the symbols 
(NetWare internal debugger command "n") shows this symbol 
"ExportedSymbolMangled" is mangled to  "W?ExportedSymbolMangled$ni". */
int ExportedSymbolMangled = 0xF1;
#ifdef __cplusplus
extern "C" {
#endif
/*  This symbol name will not be mangled if enclosed within "extern C..."
constructs. Watcom compiler will define __cplusplus by default if compiling
a .CPP file. */
int ExportedSymbolUnMangled = 0xF0;
#ifdef __cplusplus
};
#endif
// ************************************************************************
// ************************************************************  NI_Context
// ************************************************************************
NI_Context::NI_Context() 
  {
  nwContext = NWDSCreateContext();
  nwStatus = nwContext;
  bLoggedIn = FALSE;
  }
NI_Context::~NI_Context()
  {
  // logout before freeing of the context
  if (bLoggedIn == TRUE)
    NWDSLogout();
  if (nwContext != ERR_CONTEXT_CREATION) 
    {
    nwCode = NWDSFreeContext(nwContext);
    nwStatus = nwCode;
    }
  }
NWDSCCODE NI_Context::SetFlags(int nFlags)
  {
  NWDSCCODE nwReturnValue;
  /* When using NWDSSetContext() to set the context, its third parameter is
  either an integer or a character string and is prototyped as a void *.
  Hence, we need to type cast the parameter to a void *. */
  nwReturnValue = NWDSSetContext(nwContext, DCK_FLAGS, (void *) &nFlags);
  nwStatus = nwReturnValue;
  return nwReturnValue;
  }
NWDSCCODE NI_Context::SetConfidence(int nConfidence)
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = NWDSSetContext(nwContext, DCK_CONFIDENCE, 
    (void *) &nConfidence);
  nwStatus = nwReturnValue;
  return nwReturnValue;
  }
NWDSCCODE NI_Context::SetNameContext(char *charNameContext) 
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = NWDSSetContext(nwContext, DCK_NAME_CONTEXT, 
    (void *) charNameContext);
  nwStatus = nwReturnValue;
  return nwReturnValue;
  }
NWDSCCODE NI_Context::SetTransportType(int nTransportType)
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = NWDSSetContext(nwContext, DCK_TRANSPORT_TYPE, 
    (void *) &nTransportType);
  nwStatus = nwReturnValue;
  return nwReturnValue;
  }
NWDSCCODE NI_Context::SetReferralScope(int nReferralScope) 
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = NWDSSetContext(nwContext, DCK_REFERRAL_SCOPE, 
    (void *) &nReferralScope);
  nwStatus = nwReturnValue;
  return nwReturnValue;
  }
NWDSCCODE NI_Context::NWDSLogin(NWDS_FLAGS optionsFlag, char *objectName,
  char *password, NWDS_VALIDITY validityPeriod)
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = ::NWDSLogin(nwContext, optionsFlag, objectName, password,
    validityPeriod);
  nwStatus = nwReturnValue;
  if (nwStatus == 0)
    bLoggedIn = TRUE;
  return nwReturnValue;
  }
NWDSCCODE NI_Context::NWDSLogout()
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = ::NWDSLogout(nwContext);
  nwStatus = nwReturnValue;
  if (nwStatus == 0)
    bLoggedIn = FALSE;
  return nwReturnValue;
  }
// ************************************************************************
// *************************************************************  NI_Buffer
// ************************************************************************
NI_Buffer::NI_Buffer()
  {
  nwCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &nwpBuffer);
  nwStatus = nwCode;
  }
NI_Buffer::~NI_Buffer()
  {
  // NWDSFreeBuf always returns 0
  nwCode = NWDSFreeBuf(nwpBuffer);
  nwStatus = nwCode;
  }
NWDSCCODE NI_Buffer::InitBuf(NI_Context &niContext, int nOperation)
  {
  NWDSCCODE nwReturnValue;
  nwReturnValue = NWDSInitBuf(niContext, nOperation, nwpBuffer);
  nwStatus = nwReturnValue;
  return nwReturnValue;
  }
// ************************************************************************
// *************************************************  DSNameCompareFunction
// ************************************************************************
/* This compare function is used for NWSNut interface. Its purpose is to cause
"[Root]" to be first in directory list and ".." to be second. All other objects
will be sorted alphabetically with NWSNut's default compare function. */
int DSNameCompareFunction(LIST *el1, LIST *el2)
  {
  int nReturnValue;
  if (strcmp((char const *)el1->text, GETMSG(MSGID_ROOT)) == 0 ||
      strcmp((char const *)el2->text, GETMSG(MSGID_ROOT)) == 0)
    {
    // "[Root]" is being compared, make it always first in the list
    //   i.e. it will always compare less than the other strings
    if (strcmp((char const *)el1->text, GETMSG(MSGID_ROOT)) == 0)
      nReturnValue = -1;
    else
      nReturnValue = 1;
    }
  else if (strcmp((char const *)el1->text, GETMSG(MSGID_DOTDOT)) == 0)
    nReturnValue = -1;
  else if (strcmp((char const *)el2->text, GETMSG(MSGID_DOTDOT)) == 0)
    nReturnValue = 1;
  else
    nReturnValue = defaultCompareFunction(el1, el2);
  return nReturnValue;
  }
// ************************************************************************
// **************************************************************  MainMenu
// ************************************************************************
int MainMenu()
  {
  int nSelection;
  // create menu
  NWSInitMenu (handle);
  // set function for default compare
  // this will cause the strings placed in the menu to be ordered according
  //   to our insertion order (and ignore the default sorting function)
  NWSSetDefaultCompare(handle, NULL);
  NWSAppendToMenu(MSGID_MENUOPT_SERVERINFO, 1, handle);
  NWSAppendToMenu(MSGID_MENUOPT_BROWSETREE, 2, handle);
  NWSAppendToMenu(MSGID_MENUOPT_EXIT, 3, handle);
  nSelection = NWSMenu(MSGID_MENU_MAIN, 10, 40, NULL, NULL, handle, NULL);
  NWSDestroyMenu (handle);
  return nSelection;
  }
// ************************************************************************
// ************************************************************  ServerInfo
// ************************************************************************
int ServerInfo()
  {
  char strCompanyName[80];
  char strRevision[80];
  char strRevisionDate[24];
  char strCopyrightNotice[80];
  int maxChars = 80+80+24+80+3;
  char *bigBuff = new char[maxChars]; // allocate string for all strings
  nwCode = GetFileServerDescriptionStrings(strCompanyName, strRevision,
    strRevisionDate, strCopyrightNotice);
 
  // copy all strings to big string
  strcpy(bigBuff, strCompanyName);
  strcat(bigBuff, "\n");
  strcat(bigBuff, strRevision);
  strcat(bigBuff, "\n");
  strcat(bigBuff, strRevisionDate);
  strcat(bigBuff, "\n");
  strcat(bigBuff, strCopyrightNotice);
  // display big string
  NWSViewText(10, 40, 5, 50, MSGID_SERVERINFO, (unsigned char *)bigBuff,
    maxChars, handle);
  delete bigBuff;
  return 0;
  }
// ************************************************************************
// ******************************************************  BrowseTreeAccess
// ************************************************************************
int BrowseTreeAccess(char *strObject, char *charObjectSelected)
  {
  int err;
  LIST *selectionElement;
  NWSInitList(handle, NULL);
  // save NWSNut's default compare function
  NWSGetDefaultCompare(handle, &defaultCompareFunction);
  // set this object list compare function to our own "DSNameCompareFunction"
  NWSSetDefaultCompare(handle, DSNameCompareFunction);
  NWSAppendToList(NWSGetMessage(MSGID_ROOT, &(handle->messages)), 
    (void *) 0, handle);
  // selectionElement identifies the default selection for NWSList
  selectionElement = 
    NWSAppendToList(NWSGetMessage(MSGID_DOTDOT, &(handle->messages)), 
    (void *) 0, handle);
  // read objects
  NI_Context niContext;
  NI_Buffer  niBufResults;
  // set DS context to root; object name will be a Relative Distinguished Name
  niContext.SetNameContext("[Root]");
  int32               iterHandle=-1L;
  int                 i;
    uint32                    totObjects, totAttrs;
    Object_Info_T           objectInfo;
  char                objName[MAX_DN_CHARS];
  char                listObj[MAX_DN_CHARS];
  char                strBuf[MAX_DN_CHARS+2];
  // Call to NWDSList and check for errors.  iterHandle must be initialized
    // to -1.  NWDSList should be called until iterHandle is -1.  In the case
  // that the result spans multiple output buffers, iterHandle will be
  // something other than -1.  Do not change the value returned.  Just send
  // it back with the next call to NWDSList.
  iterHandle = -1L;
    do
    { 
      err = NWDSList(niContext, strObject, &iterHandle, niBufResults);
        if(err<0)
          throw("NWDSList Error");
      // Pull information from output buffer.  Check for errors.
      // First get the number of objects in the buffer.  Check for errors.
      err = NWDSGetObjectCount(niContext, niBufResults, &totObjects);
      //printf("Total objects = <%d>\n",totObjects);
      if(err<0)
      throw("NWDSGetObjectCount Error");
      for(i=0;i<totObjects;i++)
        {
        // Pull object name, total number of attributes associated with
        // object, and an object info structure from buffer. Check
            // for errors.
      err = NWDSGetObjectName(niContext, niBufResults, objName, &totAttrs, 
                                                                 &objectInfo);
          if(err<0)
        throw("NWDSGetObjectName Error");
          //printf("Object found --> %s\n",objName);
      if (objectInfo.objectFlags & DS_CONTAINER_ENTRY)
        {
        strcpy(strBuf, "+");
        }
      else
        {
        strcpy(strBuf, "");
        }
      strcat(strBuf, objName);
      NWSAppendToList((unsigned char *)strBuf, (void *) 0, handle);
      }
    } while(iterHandle != -1);
  nwCode = NWSList(MSGID_MENU_DSBROWSE, 14, 40, 11, 40, 
    M_ESCAPE | M_SELECT, &selectionElement,
    handle, NULL, NULL, 0);
  strcpy(charObjectSelected, (const char *)selectionElement->text);
  NWSDestroyList(handle);
  return nwCode;
  }
// ************************************************************************
// ************************************************************  BrowseTree
// ************************************************************************
int BrowseTree()
  {
  int  err;
  char strObject[MAX_DN_CHARS];
  char strOldObject[MAX_DN_CHARS];
  LIST *selectionElement;
  char strTemp[MAX_DN_CHARS];
  BOOL bTraversalRequest;
  LONG lContextDisplayPortal;
  strcpy(strObject, GETMSG(MSGID_ROOT)); //"[Root]");
  do
    {
    strcpy(strOldObject, strObject);
    lContextDisplayPortal = NWSDisplayInformation(MSGID_CONTEXT, 0, 2, 40, 
      NORMAL_PALETTE, VNORMAL, (unsigned char *)strObject, handle);
    err = BrowseTreeAccess(strObject, strObject);
    NWSDestroyPortal(lContextDisplayPortal, handle);
    if (err != 1)
      {
      if (strcmp(strObject, GETMSG(MSGID_ROOT)) == 0)
        {
        // clear the old object name so it will not get appended 
        strcpy(strOldObject, "");
        }
      else if (strcmp(strObject, "..") == 0)
        {
        char *strPtr;
        // check if [Root] was selected
        if (strcmp(strOldObject, GETMSG(MSGID_ROOT)) != 0)
          {
          // [Root] was not selected
          // copy old path, the new path (strObject) is ".."
          strcpy(strTemp, strOldObject); 
          strPtr = strchr(strTemp, '.');
          if (strPtr == NULL)
            {
            // we are at the root because no . was found
            strcpy(strObject, GETMSG(MSGID_ROOT));
            }
          else
            {
            strPtr++; // pass the .
            strcpy(strObject, strPtr);
            }
          }
        else
          {
          // if already at the root, set strObject to "[Root]"
          strcpy(strObject, GETMSG(MSGID_ROOT));
          }
        }
      else if (strObject[0] == '+')
        {
        int nLen = strlen(strObject);
        int nIndex = 0;
        // copy object (minus +) into temp
        for (nIndex = 0; nIndex < nLen-1; nIndex++)
          strTemp[nIndex] = strObject[nIndex+1];
        strTemp[nIndex] = '\0';
        // copy into strObject
        strcpy(strObject, strTemp);
        // add old object if not at root already
        if (strcmp(strOldObject, GETMSG(MSGID_ROOT)) != 0)
          {
          // add .
          strcat(strObject, ".");
          strcat(strObject, strOldObject);
          }
        }
      else
        // just in case a noncontainer is selected (emulate .)
        strcpy(strObject, strOldObject); 
      }
    } while (err != 1); // do until escape is pressed in browse list
  return err;
  }
// ************************************************************************
// ******************************************************************  main
// ************************************************************************
main() 
  {    
  LONG messageCount, languageID;
  LONG l1=LoadLanguageMessageTable(&messageTable, &messageCount, &languageID);
  char     *charUserName = "Admin";
  int      err;
  int      nMainMenuSelection;
  LONG     NLMHandle;
  LONG     allocTag;
  try

    {
    NLMHandle = GetNLMHandle();
    // create a screen for displaying our information 
    CLIBScreenID = CreateScreen(GETMSG(MSGID_DSBROWSE_CPP), 
      AUTO_DESTROY_SCREEN);
      if (!CLIBScreenID)
          return -1;
    DisplayScreen(CLIBScreenID);
    allocTag = AllocateResourceTag(NLMHandle, 
      (unsigned char *) "DSBrowse Alloc Tag", 
      AllocSignature);
    nwCode = NWSInitializeNut(MSGID_DSBROWSE_CPP, 
      MSGID_PROGRAM_VERSION, NORMAL_HEADER, NUT_REVISION_LEVEL, 
      0, 0, CLIBScreenID, allocTag, &handle);
    int nMainMenuSelection;
    do
      {
      nMainMenuSelection = MainMenu();
      switch(nMainMenuSelection)
        {
        case 1:
          ServerInfo();
          break;
        case 2:
          BrowseTree();
          break;
        default:
          break;
        }
      // exit if ESCAPE is pressed or Exit option is selected from main menu
      } while ((nMainMenuSelection != 3) && (nMainMenuSelection != -1));
    } // end try
  catch (int nValue)
    {
    ConsolePrintf("%s ==> ", GETMSG(MSGID_DSBROWSE_CPP));
    ConsolePrintf(GETMSG(MSGID_ERR_EXCEPT_INTEGER));
    ConsolePrintf("%d\n", nValue);
    }
  catch (NI_Context exContext)
    {
    ConsolePrintf("%s ==> ", GETMSG(MSGID_DSBROWSE_CPP));
    ConsolePrintf(GETMSG(MSGID_ERR_EXCEPT_CONTEXT));
    ConsolePrintf("\n");
    }
  catch (char * strMessage)
    {
    ConsolePrintf("%s ==> ", GETMSG(MSGID_DSBROWSE_CPP));
    ConsolePrintf(GETMSG(MSGID_ERR_EXCEPT_STRING));
    ConsolePrintf("%s\n", strMessage);
    }
  catch (...)
    {
    ConsolePrintf("%s ==> ", GETMSG(MSGID_DSBROWSE_CPP));
    ConsolePrintf(GETMSG(MSGID_ERR_EXCEPT_OTHER));
    ConsolePrintf("\n");
    }
  NWSRestoreNut(handle);
  nwCode = DestroyScreen(CLIBScreenID);
  return 0;  
  }  // end main
DDJ

Copyright © 1995, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.