Using NetWare Distributed Printing Services (NDPS)
Articles and Tips: article
Developer Support Engineer
Developer Support
01 Jan 1997
Discusses the major components of NDPS along with a description of the API classifications. Provides sample code and some practicle development tips.
Introduction
The goal of this article is to help developers start using the NDPS (NetWare Distributed Printing Services) API set. I will give a brief overview on NDPS major components and explain how the new printing model differs from the previous model. This is followed by a description of the API classifications which will introduce you to different functions of the APIs. Then I will discuss the housekeeping APIs and the error handling functions. To demonstrate the usage of some of these functions, I will go through submitting a print job in this new distributed printing model using sample code. The last section provides some practical development tips.
NDPS (NetWare Distributed Print Services) used an object-oriented informational model to represent the characteristics, status, and behavior of a print service provider. The major components of NDPS are the following:
Printer Agent (PA)
Print Service Manager (PSM)
Broker (Broker)
Printer gateways
The Print Device Subsystem
The Port Handler
Printer Agents (PA)
Printer Agents (PAs) are software processes running on a network attached node (i.e., a server or a printer) that provides a common interface for all kinds of diverse physical devices or non-NDPS aware print systems. The three components of legacy queue-based printing are now combined into a single intelligent entity, called a Printer Agent. A PA which represents a physical printer can enable a print device capable of bidirectional communication to return information regarding its availability status and other characteristics. A print client can query a printer agent about a print job or document, or about attributes of the print device.
A PA that is not represented in Novell Directory Services (NDS) is a Public Access Printer and the PA that is represented by one or more NDS printer objects is a Controlled Access Printer. A Controlled Access printer provides printing security by validating the service requests of a client.
Printer Service Manager (PSM)
Printer Service Manager (PSM) is responsible for the management of PAs. It can create, start up, shut down, and delete a PA. There is only one PSM per network node. Each PSM implements a common Managed Object Database (MOD) for all its supported PAs A PSM is implemented as an NLM (NDPSM.NLM) which will be loaded on a NetWare Server.
NDPS Broker (Broker)
NDPS Broker provides three major functions:
Service Registry Service
allows printers to register themselves so that they can be located
maintains device specific information
supports operations such as listing, registering, and deregistering devices.
Event notification Service
allows printers to send notifications to users and operators about printer events and print job
supports a variety of notification delivery methods, including pop-up windows, log files, E-mail, and programs developed by third parties.
Resource management Service
allows resources to be installed in a central location and downloaded to either clients, printers, or any other entity on the network that needs that resource.
supports adding, listing, replacing, and deleting resources including printer drivers, printer definition files, banners, and fonts.
The Broker logs in and authenticates itself on behalf of each of the above services. The broker NLM is automatically loaded during installation. On large networks with multiple servers, it is recommended that additional brokers are created so that services are not stored more than three router hops from the printer agent being serviced. This reduces network traffic and enhances performance.
Print Device Subsystem
A Print Device Subsystem translates the device-independent information received from the printer agent into a device-specific language that can be used to communicate with the physical printer. PDS maps attributes information from a Printer Agent into a printer specific job-control language such as PCL or PostScript. Each physical printer has its own PDS, port handler, and printer agent. A single server has only one PDS NLM, one port handler NLM and one NDPSM NLM. However, they can spawn multiple instances of these modules to support different physical printers.
The Port Handler
The Port Handler ensures that the Print Device Subsystem can communicate with the printer regardless of what type of physical port (parallel or serial or network) is being used.
Printer Gateways
Software bridges which provide the link between NDPS and proprietary print protocols. Gateways allow non-NDPS aware clients to submit print jobs to the NetWare environment and allow NDPS clients to submit jobs to non-NDPS printers. A gateway replaces the Print Device Subsystem and the Port Handler.
Managed Object Database (MOD)
For database support, the PSM implements a common Managed Object Database (MOD) which contains the class definitions of some NDPS components. Object Identifiers (OIDs) are used to identify a class, an attribute, a value of an attribute (syntax OIDs are not used for NDPS). The objects defined in MOD are based on open industry standards including ISO 10175-DPA and RFC 1759-Printer MIB.
NDPS Interaction with NDS Objects
For directory lookup, some NDPS components are represented in NDS. The NDS object for a Print Service Manager is an "NDPS manager," for a Printer Agent is an "NDPS Printer" object (PO), and for a Broker is an "NDPS Broker" (BO) object. When NDPS components are represented in NDS, the objects can be distributed across multiple sites on the network and different containers within the NDS tree. Sometimes, multiple POs are created for a PA so that a printer can have different configurations such as different limits of number of copies, or different control access attributes.
The NDPS job Configurations attribute in the PO store one or more job configurations for Controlled access Printer. The job configurations are also stored in attributes in MOD. The NDPS manager object in NDS stores information such as the location of the MOD and the locations of the POs for each of its associated PA. The Broker object in NDS (BO) represents the broker to NDPS clients through NDS. It uses SAP (Service Advertising Protocol) to advertise itself to non-NDS clients. Each broker will locate other brokers to share registry information.
APIs Classification
The NDPS Cross Platform Library API comprises APIs from the following functional areas:
CLibrary "Housekeeping" |
NWDPLibxxxxx |
"Housekeeping"refers to the basic functions required forany NDPS application. They provide functionssuch as memory management, and library versionreporting. |
Name Service Access |
NWDPNSrvxxxxx |
Nameservice APIs consist of calls intended to establish context for implicit Name Serviceoperations in other calls. |
Print Intrinsic |
NWDPPrtxxxxx |
TheseAPIs facilitate searching, creating, querying and modifying of printer objects both inthe name service and installed on the user's workstation. |
Job |
NWDPJobxxxxx |
TheseAPIs facilitate searching, creating, querying and modifying of jobs in the printer's jobpool. |
Document |
NWDPDocxxxxx |
TheseAPIs facilitate searching, creating, querying and modifying of documents contained withinjobs. |
DPS Manager |
NWDPPsmxxxxx |
Thefollowing APIs control the DPS Manager functions that reside outside a virtual printer. NDPS Manager allows administrators to create,configure, start up, or stop printer agents. |
Filters |
NWDPFltxxxxx |
Filtersare used for searching for specific attributes of jobs and printers. These APIs facilitate creating and managing filters. |
Attribute Set |
NWDPASxxxxx |
AttributeSet is used to define the attributes for NDS objects. These APIs facilitate creatingand managing lists of attributes and their values for an object. |
Oid |
NWDPOidxxxxx |
ObjectIdentifiers (OIDs) can be used to define classes, attributes, syntaxes, and even valuesof attributes. These APIs facilitate creating and managing of Object Identifier sets. |
Broker Administration |
NWDPBrkxxxxx |
TheseAPIs facilitate the Support Services Broker administration. |
Service Registry |
NWDPSrsxxxxx |
TheseAPIs facilitate the Service Registry administration. |
Notification Service |
NWDPNtfyxxxxx |
TheseAPIs facilitate searching, creating, queryingand modifying of notification profiles and notification methods. |
Resource Service |
NWDPResxxxxx |
TheResource Management Service (RMS) APIs provideaccess to common resources stored on thenetwork. Resources supported in the firstrelease of NDPS are printer drivers, printerdefinition files, banner pages, and fonts. |
Housekeeping Library
NWDPLibInit. This function initializes the library by allocating space on a per task/thread-group basis, creating the accessor and maintaining copies of "global" values. TaskID and SubTaskID are for multiple threads or processes to share memory allocation. SubTaskID is used to store unique data for processes with the same TaskID. They are arbitrary numbers provided by the application which will create multiple accessor references per task or thread group or process. The application is responsible for ensuring that the accessor references it created are released by calling NWDPLibTerm for every NWDPLibInit with a different taskID/subTaskID pair. Repeat usage of a taskID/subTaskID pair causes the same accessor reference to be returned and a usage count to be incremented. It also provokes a warning error code NWDP_IC_WARN_REPEAT_ID. For a DOS application where multiple processes are irrelevant, the application should pass in zero.
NWDPLibInit is the only API that handles error code differently from the other APIs. The sample program in this article includes a subroutine that checks for the possible return codes.
NWDPLibTerm. This function terminates the library=s usage for the associated task and SubTask IDs (see NWDPLibInit) decrementing a usage counter. If the usage counter goes to zero it also deallocates all overhead space allocated to the accessor reference.
Error Handling
In order to have useful debugging information, you must define N_DEBUG. The Return Values for all API (except NWDPLibInit) are N_SUCCESS(0)for success, N_FAILURE( 1)for failure, NWDP_RC_WARNING(1)for warning, and NWDP_RC_INVALID_ACCESSOR( 2)for an invalid accessorRef (if N_DEBUG is defined). The really meaningful error code is actually stored in the structure pointed to by NWDPAccessorHdl. The publicly available fields of NWDPAccessorData struct should be accessed through the following macros.
#define NWDPLibErrorMac(accessorRef) (((NWDPAccessorData *)accessorRef) >libError) #define NWDPOtherErrorMac(accessorRef) (((NWDPAccessorData *)accessorRef) >otherError) #define NWDPOtherError2Mac(accessorRef) (((NWDPAccessorData*)accessorRef) >otherError2) #define NWDPOtherErrorStrMac(accessorRef) (((NWDPAccessorData *)accessorRef) >otherErrorStr) #define NWDPErrorModuleMac(accessorRef) (((NWDPAccessorData *)accessorRef) >errorModule) #define NWDPErrorLineMac(accessorRef) (((NWDPAccessorData *)accessorRef) >errorLine)
The libError field is always valid and contains either a pertinent library error code or an "other" error category. Error categories indicate that the error occurred in a call to an underlying routine. The library where the routine resides is identified by the error category code and the actual error from that library is stored in the otherError fields. If the libError field does not contain an error category code, then the otherError field contains no valid data.
The otherError field will be valid if the 0x01000000 bit is set in the libError field (use NWDP_EC_CATEGORY_MASK). If the 0x02000000 bit of libError is also set, then the otherError2 field is also valid, indicating that the subordinate library also returned a second error value (use NWDP_EC_CATEGORY2_MASK). If the 0x04000000 bit of libError is set along with the 0x01000000 bit, then the otherErrorStr field will point to an explanatory, null terminated string returned from the subordinate library (use NWDP_EC_CATEGORYSTR_MASK).The macro NWDPDisplayErrorMac defined in NWDPERR.h provides a cross-platform error checking routine. In the development environment, the debug version dlls and libraries should be used and N_DEBUG has to be defined so that you can get valuable debugging information from the errorModule and errorLine fields.
#define NWDPDisplayErrorMac(accRef) { char buf1[250]; pnchar buf2Ptr; if ((NWDPLibErrorMac(accRef) & NWDP_EC_CATEGORY_MASK) == NWDP_EC_CATEGORY_MASK) { switch (NWDPLibErrorMac(accRef)) { NWDPLibCategoriesListMac default: /* Should never get here but could with a newer library*/ buf2Ptr = &buf1[239];& SprintfMac(buf2Ptr,"%#8lX",NWDPLibErrorMac(accRef)); break; } if ((NWDPLibErrorMac(accRef) & NWDP_EC_CATEGORY2_MASK) == NWDP_EC_CATEGORY2_MASK) { SprintfMac(buf1,"Error category: %snReported errors: %ld,%ldn" "Library module: %snLibrary line: %ld", NWDPOtherErrorMac(accRef), NWDPOtherError2Mac(accRef), NWDPErrorModuleMac(accRef), NWDPErrorLineMac(accRef)); } else if ((NWDPLibErrorMac(accRef) & NWDP_EC_CATEGORYSTR_MASK) == NWDP_EC_CATEGORYSTR_MASK) { SprintfMac(buf1,"Error category: %snReported error: %ldn" "Error string: %sn" "Library module: %snLibrary line: %ld", NWDPOtherErrorMac(accRef), NWDPOtherErrorStrPtrMac(accRef), NWDPErrorModuleMac(accRef), NWDPErrorLineMac(accRef)); } else { SprintfMac(buf1,"Error category: %snReported error: %ldn" "Library module: %snLibrary line: %ld", buf2Ptr, NWDPOtherErrorMac(accRef), NWDPErrorModuleMac(accRef), NWDPErrorLineMac(accRef)); } } else { if (NWDPLibErrorMac(accRef) == NWDP_LE_OTHER_NDPS_ERROR_RETURN) { SprintfMac(buf1,"Library Error: NWDP_LE_OTHER_NDPS_ERROR_RETURNn" " NWDPErrorReturn.designator (see NWDP_ATT.H): %dn" "Library module: %snLibrary line: %ld", accRef >otherNDPSErrorReturnPtr >designator, NWDPErrorModuleMac(accRef), NWDPErrorLineMac(accRef)); } else { SprintfMac(buf1,"Reported NWDP_LE_xxxx error: %#8lXn" "Library module: %snLibrary line: %ld", NWDPLibErrorMac(accRef), NWDPErrorModuleMac(accRef), NWDPErrorLineMac(accRef)); } } MESSAGE_BOX(buf1,"NWDPDisplayErrorMac Report"); } #endif
Sample Code:NDPSSJOB.C
This program assumed that you have created a PSM object, a printer object, and has assigned the printer object to a printer agent. A label (shortlist name) must also be created by installing the printer to a shortlist before you run this program.
Initialize double bytes table and low level API interface functions.
code = NWCallsInit(NULL,NULL); if (code) { printf ("ERROR: NWCALLSInit returned ccode=%x\n",ccode); return(N_FAILURE) ; }
Invoke NWDPLibInit to allocate memory on a pertask/thread group basis and to create the accessor. SinceNWDPLibInit handle error differently than the other APIs, NWDPLibInitMacwas defined in NWDP_LIB.h to check for all the possible errorcodes. A return code that is less than 0 is fatal and greater than 0 is only a warning.
__retCode = NWDPLibInit( NWDP_LIBRARY_MAJOR_VERSION, NWDP_LIBRARY_MINOR_VERSION, NWDP_LIBRARY_BUGFIX_REVISION, NWDP_LIBRARY_PREREL_VERSION, __taskId, __subTaskId, accRefPtr, __actualMajorVersion, __actualMinorVersion, __actualBugfixRevision, __actualPrereleaseVersion ); /* if return code is less than zero, it is a fatal error */if (__retCode < 0) { switch(__retCode) { case NWDP_IC_ERR_NO_MEMORY: __buf2Ptr = "NWDP_IC_ERR_NO_MEMORY"; break; case NWDP_IC_ERR_NO_ACCESSOR: __buf2Ptr = "NWDP_IC_ERR_NO_ACCESSOR"; break; case NWDP_IC_ERR_WRONG_MAJOR_VERSION: __buf2Ptr = "NWDP_IC_ERR_WRONG_MAJOR_VERSION"; break; case NWDP_IC_ERR_OLD_MINOR_VERSION: __buf2Ptr = "NWDP_IC_ERR_OLD_MINOR_VERSION"; break; case NWDP_IC_ERR_OLD_REVISION: __buf2Ptr = "NWDP_IC_ERR_OLD_REVISION"; break; case NWDP_IC_ERR_IS_PRERELEASE: __buf2Ptr = "NWDP_IC_ERR_IS_PRERELEASE"; break; case NWDP_IC_ERR_OLD_PRERELEASE: __buf2Ptr = "NWDP_IC_ERR_OLD_PRERELEASE"; break; case NWDP_IC_ERR_UNICODE_INIT: __buf2Ptr = "NWDP_IC_ERR_UNICODE_INIT"; printf("NWDPLibInit returned error: %d %sn" "NLS subdirectory is not in the PATH.", __retCode, __buf2Ptr ); break; default: switch (__retCode) { case NWDP_IC_ERR_NWCALLS_INIT: __buf2Ptr = "NWDP_IC_ERR_NWCALLS_INIT"; break; default: /* Should never get here */ __buf2Ptr = "new error"; break; } printf("NWDPLibInit returned error: %d %s", __retCode, __buf2Ptr ); break; } /* Warning if return code is greater than 0 */ else if (__retCode > 0) { switch(__retCode) { case NWDP_IC_WARN_REPEAT_ID: __buf2Ptr = "NWDP_IC_WARN_REPEAT_ID"; printf("NWDPLibInit returned warning: %d %sn" "TaskId: %un SubTaskId: %un", __retCode,__buf2Ptr,__taskId,__subTaskId); break; default: switch (__retCode) { case NWDP_IC_WARN_VER_OBSOLETE: __buf2Ptr = "NWDP_IC_WARN_VER_OBSOLETE"; break; default: /* Should never get here */ __buf2Ptr = "new warning"; break; } printf("NWDPLibInit returned warning: %d %s", __retCode, __buf2Ptr); break; } } return(__retCode); }
Initialize unicode tables. All information in the NDS database is stored in Unicode format which provides universal language support. A workstation has to translate the unicode character to local code and vice versa according to the Unicode table that are initialized by NWInitUnicodeTable with the proper country code and code page.
NWLlocaleconv(&lconv);& if ((ccode = NWInitUnicodeTables( lconv.country_id,lconv.code_page)) != 0) { printf ("ERROR: initializing unicode tables ccode=%xn",ccode); return(N_FAILURE); }
Create Directory Service context and turn off the translate string flag so that unicode string will not be automatically translated. Since all the internal processing is operated on the unicode format and the only reason for a unicode string to be converted is for display, it is much more efficient for the NDPS library to operate on unicode format. Therefore, since the default context flag is set to perform translation, NDPS application has to turn off the automatic translation by explicitly resetting DCK_XLAT_STRINGSin DCK_FLAGS for the DS context.
contextHandle = NWDSCreateContext(); if(contextHandle == ERR_CONTEXT_CREATION) { printf("Could not create context"); return(N_FAILURE); } ccode = NWDSGetContext(contextHandle, DCK_FLAGS, &contextFlags);& if (ccode) { printf("NWDSGetContext return %x",ccode); return(N_FAILURE); } contextFlags = (contextFlags | DCV_TYPELESS_NAMES) & ~(DCV_XLATE_STRINGS);& ~(DCV_XLATE_STRINGS); ccode = NWDSSetContext(contextHandle, DCK_FLAGS, &contextFlags)& ;if (ccode) { printf("NWDSSetContext returned %x",ccode); return(N_FAILURE);}
Set Directory Service context. Invoke NWDPNSrvSetNativeNDSContext to provide the NDS context variable to be used in all implicit references to Name Service objects. The caller must already be logged in and authenticated. The programmer should be careful when altering the contents of the NDS context after this call is made because all NWDPxxxxx calls which follow this depend on that same value. No information other than the value of the contextHandle is preserved in the structure referenced by the accessorRef.
if (NWDPNSrvSetNativeNDSContext(accRefGbl, contextHandle)) { NWDPDisplayErrorMac(accRefGbl); return(N_FAILURE);}
NWLocalToUnicode - Since translate string is turned off, NDPS application is responsible for converting all input parameters to the unicode format before passing them to the library.
if (NWLocalToUnicode( NWDPLocalToUnicodeHandleMac(accRefGbl), label16, sizeof(label16)/sizeof(nchar16), (pnuint8)label, '_',&numOfNChar16sInResult))& return(N_FAILURE);
Invoke NWDPPrtCreateRefBasedOnLabel to create a printer reference based on an installed printer's label. The label is given to the Printer Agent when it is installed to a shortlist.
if (NWDPPrtCreateRefBasedOnLabel(accRefGbl, label16,&printerRef))& return(N_FAILURE);
Invoke NWDPPrtGetJobConfig to retrieve a configuration associated with the referenced printer. This call will fail if the printer has not installed to a shortlist.
if (NWDPPrtGetJobConfig( accRefGbl, printerRef, 0, NULL NULL, &jobAttrSetRef,& &jobAvpRef,& &docAttrSetRef, &docAvpRef))& return(N_FAILURE);
The job and document can be custom defined by adding attribute to their respective attribute set. In this example,the job name is added to the job attribute set and the document name is added to the document attribute set. A printer can contain many jobs. Jobs can contain many documents. In the first release of NDPS, a job can only contain one document, but it is designed to support multiple documents in a job in future release. The job is deleted when it completes printing or is canceled by the user and its retention time has expired.
/* add Job Name attribute */ if (NWDPASAddAttribute(accRefGbl jobAvpRef,pNDPSATT_JOB_NAME(accRefGbl),NULL)) return(N_FAILURE); /* specify job name syntax/designator and invoke Macro to assign its value to the file name*/ value.designator = NWDP_AVT_SIMPLE_NAME; NWDPTextPtrInitMac(&value.u.simpleName, file16);& if (NWDPASAddAttrValue(accRefGbl,jobAvpRef,NULL,&value))& return(N_FAILURE); /* add Document Name attribute */ if (NWDPASAddAttribute(accRefGbl,docAvpRef, (pNWDPOid)NDPS_ATT_DOCUMENT_NAME,NULL)) return(N_FAILURE); /* specify document name syntax/designator and invoke Macro to assign its value to the file name , this is the only document in the job */ value.designator = NWDP_AVT_TEXT; NWDPTextPtrInitMac(&value.u.text, file16);& if (NWDPASAddAttrValue( accRefGbl,docAvpRef,NULL,&value))& return(N_FAILURE);
Create references for the job and the document and put the first (and only) document in the job.
if (NWDPJobCreateRef(accRefGbl, printerRef, jobAttrSetRef, &jobRef))& return(N_FAILURE); if (NWDPDocCreateRef(accRefGbl,jobRef,NWDP_DOC_POSITION_LAST,docAttrSetRef,&docRef))& return(N_FAILURE);
Copy data to the created document object at the virtual printer from the file specified in the file16 parameter.(NWDPDocTransferFile).The callback function ProgressFunc provides a status update. The last parameter is a user-defined contextual variable to be used by the callback routine. NULL is being passed in here.
transferCallBackParam2 = NULL; callbackPtr = ProgressFunc; if (NWDPDocTransferFile(accRefGbl, docRef, file16, callbackPtr, transferCallBackParam2) return(N_FAILURE);
TotalBytesToBeTransferred is a count of the number of bytes in the source file. CurrentNumberTransferred is the current amount of data that has already been transferred. This will approach the total BytesToBeTransferred but will not equal it until after all data has been copied. Screen cleanup should occur at this point.
/*********************************************************************** ** ProgressFunc informs you of the progress of the printing ***********************************************************************/ N_GLOBAL_CALLBACK(int) ProgressFunc( NWDPAccessorRef accessorRef, nparam transferCallbackParam2, nuint32 totalBytesToBeTransferred, nuint32 currentNumberTransferred ) { printf("\r Percent Complete: % 3u Bytes Transferred: %lu", (nuint)((((currentNumberTransferred * 1000L)/totalBytesToBeTransferred) + 5L)/ 10L),currentNumberTransferred);}
NWDPJobSubmit to submit a previously unsubmitted job. This function informs the device that the job has the attributes and data requested by the application, and that the job can be scheduled for printing. The job object must already exist on the server and all documents must be closed.
if (NWDPJobSubmit(accRefGbl, &jobRef,0))& return(N_FAILURE);
CleanUP: Destroy each job reference that is created.(NWDPJobDestroyRef) After printing the job, release the Printer reference, the Job attributeSet Reference and Document AttributeSet Reference.
if (NWDPJobDestroyRef(accRefGbl,&jobRef))& return(N_FAILURE); if (NWDPPrtReleaseRef(accRefGbl,&printerRef))& return(N_FAILURE); if (NWDPASReleaseRef(accRefGbl, &jobAttrSetRef)) & return(N_FAILURE); if (NWDPASReleaseRef(accRefGbl, &docAttrSetRef))& return(N_FAILURE);
Development Tips
There are two sets of dlls included on the SDK, files with extension .dbg are dlls with debug information. You can rename the .dbg files to .dll and use them for development.
If you do not want to hook up to a real printer for development and testing, you can capture the port to the screen. To capture the port to the screen, unload the port handler (ph.nlm) if it is loaded. At the system console, type the following commands:
load ph pa=<printer agent name< port=screen load pds pa=<printer agent name< pdef=xxxxx.npd
(The xxxxx.npd file is a printer definition file that is created when you select a printer. It should be found in system\resdir\prndef\novell area.)
The NDPS APIs are contained in the following DLLs.
DPLWINxx - Housekeeping functions,name services, NWDP_LIB - housekeeping function NWDP_NSR - name services
DPAWINxx - NWDO_OID - OID NWDP_ATR - attribute manipulation NWDP_FLT - filter manipulation
DPPWINxx - Job and document, and Print Service Management NWDP_PRT - Print intrinsic NWDP_JOB - Job manipulation NWDP_DOC - Document manipulation NWDP_PSM - Printer Agent Management
DPSWINxx - Broker functions NWDP_SRS - Service Registry NWDP_BRK - Broker Administration NWDP_RES - Resource Management NWDP_NOT - Notification
DPRPCXXX - are derivations of TIRPC which is a version of the industry standard RPC (remote procedure call) ONC RPC. Initial release of NDPS supports IPX/SPX only. Future versions will also support TCP/IP.
You should include all five NDPS dlls for your application. The sample code NDPSSJOB.C can be found on our web site http://devsup.novell.com, WIN 3.1 area of our sample code section. If you are developing for the 16-bit Windows environment, you must use client32 for Dos/Win because VLM is not supported. NDPS was in alpha phase when this article was written, so things that are described in the article are subject to change for the final release of the product.
Thanks to Craig Whittle, Roy Studyvin, Daryn Robbins, James Paxman, Spencer Cottam, and Hugo Parra for reviewing this article.
* 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.