Titanium and the NTC Ship Manager Application

NTC Ship Manager software, which Evan helped develop, is used by ship owners and managers to administer operations, improve communications, strengthen controls, and cut costs. And at the heart of the highly database-intensive transaction-processing application is MDBS's Titanium database engine.


August 01, 1997
URL:http://www.drdobbs.com/architecture-and-design/titanium-and-the-ntc-ship-manager-applic/184410397

Dr. Dobb's Sourcebook July/August 1997: Titanium and the NTC Ship Manager Application

Evan, a lead developer for Nautical Technology Corp., can be contacted at [email protected].


Nautical Technology Corp. develops vessel-management software for the marine industry. Our NTC Ship Manager software includes modules for inventory control, requisitioning and purchasing, maintenance and service ordering, preventive maintenance, invoicing and financial analysis, crew management, and crew payroll. All of these modules operate on a single, integrated database, so that, for example, in the purchasing module, you record purchases of spare parts for the same machinery items whose maintenance is tracked in the preventive-maintenance module.

The system is typically installed in one or more offices of the client company, as well as on ships. Since the high cost of satellite communications precludes ocean-going vessels from tapping directly into shoreside databases, a separate database is installed at each site (vessel or office). Our Replication Manager software ensures that data entered or changed at each site is transmitted and redistributed to all the other sites as applicable.

In short, NTC Ship Manager is a highly database-intensive transaction-processing application, incorporating over 300 different types of database transactions covering a diverse range of application areas. At the heart of the system is Micro Data Base Systems (MDBS) Titanium database engine (and its precursors -- MDBS III and MDBS IV). When NTC Ship Manager development began in 1984, we selected MDBS/Titanium because of its speed, flexibility in allowing complex data interrelationships to be accurately represented, and support for C. Since then, MDBS has continued to enhance the engine's features with support for ODBC and stored procedures, providing us a growth path for the continuing development of NTC Ship Manager.

Titanium Engine Overview

Titanium is a multithreaded database management system designed for complex applications, including those requiring rehosting of nonrelational database applications.

The system is scalable from stand-alone DOS and Windows 3.x/95/NT systems, to client/server networks running NetWare NLM, Windows 95/NT, OS/2, and UNIX servers. Version 6.1 provides interfaces for C, C++, Cobol, Visual Basic, Delphi, and includes drivers for 32-bit ODBC database access. It also provides TCP/IP protocol support. Titanium API calls are identical across all operating systems, allowing for the migration of applications without code changes.

Titanium implements a unique indexing construct called "Dynamic Pointer Arrays" (DPAs) that permits queries to be performed without joins, thereby minimizing disk I/O and providing performance benefits for operations involving two or more tables. DPA technology also addresses complex data modeling problems by directly supporting one-to-many, many-to-many, and recursive relationships without intersect tables or redundant data.

Ship Manager Overview

Our experience building NTC Ship Manager showed that there are a number of different factors that go into developing a high-quality commercial application:

NTC Ship Manager was originally developed with the MDBS III and MDBS IV database managers, precursors to today's Titanium DBMS. In our 13 years of experience with this family of database tools, we have accumulated a number of tricks and techniques which we will share with you in this article. Some of these we learned from other MDBS developers; some we developed on our own, whether by inspiration or sheer necessity. All of them are hands-on, practical techniques that work in the real world.

Database Functionality, Encapsulation, and Error Handling

The Titanium API functions use a shared set of possible return values called DMS (Data Management System) error codes. Generally, a return value of 0 indicates that the API function completed with the intended result, while a nonzero value indicates an error or special condition. Currently, there are approximately 200 such codes, most of which can be returned by more than one API function. For any given API function, there might be on the order of one or two dozen possible return values, signifying problems ranging from program logic errors to environmental issues (disk read/write errors, and so on).

Handling all these possible return values presents a potentially overwhelming burden. If you tried, for every Titanium API call in your application, to individually handle every possible return value (or even just the common ones), you would be programming for a long time. There must be a better way -- and there is. The solution lies in creating a single entry point through which all Titanium API calls in your application must pass. The specific techniques for doing this will depend on the programming language your application uses, but we will illustrate how we accomplished it in C.

We created a function called dms_call(), which takes a number of arguments. We then created a header file (see Listing One, which redefined all the Titanium API calls as macros, each of which expands to a call to dms_ call() with specific arguments. When the source code is recompiled with this header file, all calls to the Titanium API are converted to calls to dms_call(). In this way, we are able to encapsulate all NTC Ship Manager's Titanium database access into the dms_call() module.

The dms_call() function is illustrated in Listing Two. The arguments to dms_ call() include a function number that indicates the Titanium API function we are "really" trying to call. dms_call() calls the appropriate Titanium API function, and passes it the intended arguments (which were also passed as arguments to dms_ call()). It then examines the value returned by Titanium. Values of zero (indicating success) or 255 (indicating a "no more records" condition, also a "normal" result) are passed back to the caller of dms_call(). On any other values, dms_call invokes our DMS error handler, which displays a screen containing diagnostic information, allows the user to type in supplemental information for an error report, and terminates the application. This scheme allows us to write a single generic DMS error handler and attach it by default to all our Titanium API calls.

We have also left room for further fine tuning. If, in a particular situation, we want to trap and handle a particular Titanium error number ourselves, we can call another function to add the error number to an array of error numbers, which dms_ call() should ignore and pass through to its caller. For example, if we are inserting a record into a Titanium set (index), we may want to ignore DMS error 11, which means that the record is already included in the set. In this case, we precede our call to the record insertion with a call to passerr(11) to temporarily add Error 11 to the list of error codes that will not trigger the DMS error handler, then call passerr(-11) afterwards to remove Error 11 from this list; see Listing Three.

Record Creation Functions

Titanium provides a transaction-based architecture with full online commit and abort functionality, permitting the encapsulation of the creation and/or modification of records together with all related processing necessary to preserve integrity. This is important for NTC Ship Manager, since most of the approximately 100 concepts (record types or "tables" in relational parlance) in our system have specific integrity constraints; in some cases, there are multiple such constraints involving a substantial amount of complexity. For example, a newly created record may need to be inserted into a dozen or more sets, often conditionally depending on one or more parameters.

In structuring our program code, we were careful to separate out into distinct functions the Titanium API calls needed for creation of new records. These functions, which we have come to refer to as "crs-functions" after the old MDBS crs() (Create Record and Store) function used to create database records, typically handle all back-end processing associated with creating new records, including starting and committing/aborting transactions, creating the new record, inserting the new record into sets as needed, and updating any associated records. Any and all user-interface code is rigorously excluded from these functions. In doing this, we have essentially produced our own well-defined API for creating new NTC Ship Manager records.

The great benefit of this approach is reliability and robustness. In many cases, there are numerous different places in the program where a new record of a particular type could be created. For example, a new machinery part record can be typed into an input screen provided for that purpose, or imported from an external system via an Xbase file. In both cases, the ntc_crs_part() function is called to do the actual creation.

User Indicators

Titanium provides the concept of "user indicators" that can be used by the application program to "remember" specific database records. A record held in a user indicator can optionally be locked by the application. This gives you great flexibility in devising a record-locking/concurrency-control strategy.

We took advantage of these facilities by building our own record locking/unlocking API on top of them. Our ntc_lock_ record() function allocates the next available user indicator from a pool of user indicators that it manages, and assigns this user indicator to the current record. It then calls the Titanium API again to lock the record. Finally, it returns to the caller an integer handle to the user indicator, which can later be passed to ntc_unlock_ record() to unlock the record and free the user indicator for reuse. This scheme allows for nesting record locks, without requiring the locks necessarily to be released in the order that they were initiated. Because management of the pool of available user indicators is built into our API routine, the various parts of our application do not need to be concerned with what records have already been locked, or with what the user indicator handles by other portions of the application.

Global Variables for Field Names and Set Names

In the first years of NTC Ship Manager's existence in the mid-1980s, the DOS-based C compilers we used (Lattice C and, later, Microsoft C) had a limit of 64 KB on the total size of compiled-in static data. As time passed and our programs grew larger, this started to become problematic for us, because most of the database API functions took one or more string parameters denoting database field names or set names, which counted toward the 64-KB limit. The early Lattice compiler at least had a feature whereby multiple occurrences of a particular string (such as "remarks") in a single source file would generate multiple references to a single copy of the string in the object code. When we switched to the Microsoft compiler in 1988, this useful but nonstandard feature went away, and our static-data size problems became acute. At one point, we determined that 15 percent of the total size of our executable program consisted of static data from field names and set names alone!

Our solution was to do away with the practice of using quoted strings for field name and set name arguments, and to define a variable for each field name and set name that could be referenced for this purpose. We wrote a program to scan our Database Definition Language (DDL) file and parse out the name of every field and set in the database. For each unique field name used anywhere in the database ("remarks," for example), this program created a new source file, remarks.c, consisting of a single line of code:

char f_remarks[] = "remarks";.

Additionally, the program created a single header file, fldnames.h, containing an entry like the following for each field name:

extern char f_remarks[];.

We compiled all the field-name .C files into a library called FLDNAMES.LIB. We then went through our code and replaced references in our source code to "remarks" with the new variable f_remarks, so that gfc("remarks"); became gfc(f_remarks);.

When we linked our executable with the FLDNAMES.LIB, we now had no more than one actual instance of each field-name string linked into the program! Applying a similar solution to set names yielded even bigger savings, and our static-data size problems were licked for good.

You may be wondering why we went to the trouble of writing a program to parse the DDL file instead of doing it manually. The answer is that, from time to time, we modify the database schema, adding new fields and/or sets. When this happens, all we have to do is rerun the DDL parser and recompile using the new header file and .C files it generates.

The compilers nowadays can accommodate more than 64 KB of static data if need be, so this issue is not the show stopper it once was. Still, that's a lot of data adding to the size of your executable program for no good reason. While it might not justify a retrofit of an existing application, a little bit of advance planning will allow you to reap the benefits of this technique for new projects.

Redundant Sets

Database theory discusses third-normal-form representation of data, with no redundancy whatsoever, but real-world developers know that in practice, exceptions to this ideal are necessary, most commonly for reasons of expediency or performance. We have taken advantage of an opportunity to use redundancy to our advantage in our inventory-control module with respect to the classic parts-explosion (bill of material) problem.

Each piece of shipboard machinery, such as a main engine, generator, or pump, includes a list of parts. For some large and complex equipment, such as a main engine, there may be a number of assemblies and subassemblies, with several levels of breakdown, before reaching the smallest parts. The total number of parts in such equipment can be approximately 2000. For smaller machinery items, such as a pump, the total of approximately 30 parts can be represented in a one-tiered flat list. Titanium's support for recursive sets allows us to provide an accurate data representation of the multilevel parts explosion. The equipment owns the top-level parts (assemblies), which may then own subassemblies, which may, in turn, own parts. (More than three levels may be needed in practice; NTC Ship Manager allows up to 10.)

The aforementioned scheme is the most economical, third-normal-form data representation. But it did not lend itself well to the on-screen, scrolling listings of parts (complete with hierarchical levels indicated by indentation) that we wanted to provide to our users. Programming our listing feature to navigate the various levels of the recursive set would have been cumbersome, and did not fit in well with the generic listing routine we had developed (which could list a group of records, provided they were all members of the same owner record). Our solution was to tinker with the data.

We defined in the database a new set linking the owner equipment record directly to all the member parts, through all levels. We created an algorithm to insert newly created parts into this set, with the order of their appearance being the same order that you would see if you were to traverse the tree from top to bottom, following each node down to the bottom recursively before continuing with the next node. Figure 1 illustrates the original recursive set and the additional redundant set linking the equipment record directly to all parts. At the cost of a bit of tricky programming (and some storage for the new set indexes), we now had a single, complete, fast, easy-to-access set for building our onscreen hierarchical listings. These hierarchical listings have, for years, been the backbone of our inventory-control and purchasing modules and are key to their ease of use.

Performance Characteristics

You will want to "stress test" your application to monitor the kind of performance it provides, especially in processing-intensive areas. There are a number of ways to approach performance issues in Titanium, each with its own applicability and tradeoffs.

One fundamental item you will want to pay close attention to is database page buffering. With Titanium, you can specify the amount of RAM that is allocated to the DBMS for database page buffering to reduce disk accesses and increase performance -- often dramatically. This performance benefit is relatively easy to achieve. In client-server Titanium environments, you specify the number of page buffers to be allocated at the database server in a command parameter file. In stand-alone Titanium environments, your application code must specify to the DBMS the number of page buffers to be used. The most flexible way to do this is to read the desired value from a user-definable configuration file. In both cases, you (or your user) must make sure there is enough RAM to support the desired number of buffers. The number of page buffers required to deliver good performance depends on what your application is doing, as well as your hardware, software, and network environment, and is best determined by experimentation. One thousand page buffers is typically a good starting point. Although page buffering is more of an implementation issue than a development issue, it is one of the first things you want to look at, since it provides significant benefits that are easy to achieve.

One of the first performance issues we wrestled with was in developing the hierarchical parts listings described previously. Each part was displayed on one line, together with its primary part number. Some explanation is in order here. NTC Ship Manager allows multiple part numbers to be entered for each part; each part number is linked to a reference describing what kind of part number it is (usually, this is the name of a vendor or the title of the equipment manual that the part number was published in). This design, while highly flexible and useful, required that the part number be stored in a different record type from the part, and related by a set. When we ran our hierarchy listing, we found it to be noticeably slow. Upon investigating, we found that the performance problem was caused by thrashing; the part number records were apparently being stored in a location on the hard disk that was physically distant from the part records. Once we understood the problem, the fix was not difficult. We modified our DDL specification to indicate that part number records should be clustered around their owner parts. This directive causes the Titanium database manager to store new part number records on the same database page as their owner part records wherever possible. As a result, the thrashing stopped, and the hierarchical listings ran much faster.

A performance issue of a different kind surfaced when we were developing our first client-server implementation of NTC Ship Manager in 1991. A frequently used screen listing all ship records in the database, which had always performed perfectly well in our stand-alone implementation, ran unacceptably slow in a client-server configuration using the Netbios protocol. With the help of a protocol analyzer, we determined that the bottleneck was caused by a large number of data packets being exchanged between the client and server. When we started experimenting, we found that we could improve performance dramatically by replacing the "Get current record" command we were using to retrieve the ship record from the server with three "Get field from current record" commands to read just the three fields we needed. This was contrary to what we had expected -- we had anticipated that one get-record command would be faster than three get-field commands, in view of the associated per-command overhead. But the point was that the ship record was one of the largest records in our database schema, and the database manager was breaking it up into multiple data packets to transmit across the network. The single get-record command was producing more network traffic -- in data and acknowledgments -- than the three get-field commands! We changed the code, and solved the problem; and we changed our programming approach to favor get-field commands over get-record commands, especially when dealing with large records. But the important lesson here is that it's worthwhile to try these sorts of things both ways, because it is hard to predict in advance which way is going to be faster; the answer is dependent on the specifics of your environment and what the application is trying to do.

Conclusion

The last, but far from least, piece of advice I have for you in preparing to develop your application is that, if you hope to take full advantage of Titanium's technology, surround yourself with the best developers you can find. To extract the high performance that Titanium can deliver requires a corresponding degree of technical sophistication on the part of the designer and programmer. At NTC, our good fortune in assembling and retaining a long-tenured team of strong developers has been crucial to our product's success.

DDJ

Listing One

/********************************  This file redefines all the MDBS functions as calls to the Command
*  Shell.  This shell uses the function dms_call(), declared as follows.
*      int dms_call (fptr, arg1, arg2, fnum, filename, linenumb)
*            int (far pascal  *fptr)();
*            char             *arg1, *arg2;
*            int              fnum;
*            char             *filename;
*            unsigned int     linenumb;
*   fptr           is a pointer to the MDBS function
*   arg1 and arg2  are pointers to the arguments which get passed to fptr
*                  (If there are fewer than 2 arguments, a pointer to bl_p,
*                  which is defined as  " ",  pads the unnecessary arguments.)
*   fnum           is a unique number assigned to each MDBS function
*                  (for debugging)
*   filename       is a pointer to the filename returned by the preprocessor
*                  (This string is only included when DEBUG is defined, in
*                  order to save space; otherwise the null pointer is passed.)
*   linenumb       is the line number within the file (as determined by the
*                  preprocessor)
*    N.B.:  Because all MDBS functions use the pascal calling convention,
*          it was necessary to reverse the 2nd and 3rd arguments in dms_call
*          (i.e.,  "a, NULL"  becomes  "NULL, a")  so that functions which
*          expect only a single argument will find it in the correct place
*          on the stack.
*******************************/


#ifndef NTC_DMS_FILENAME static char *sourcefilename = NULL; #endif

typedef ushort UI; // user indicator #define DMS_INVALID_OWNER 5 // DMS error for invalid owner #define DMS_INVALID_MEMBER 6 // DMS error for invalid member

//NULL, 0 NULL, NULL, #define DMS_AMM 1 #define DMS_AMO 2 #define DMS_AOM 3 #define DMS_AOO 4 #define DMS_AUI 5 #define DMS_CCU 6 #define DMS_CRA 7 #define DMS_CRS 8 #define DMS_DBCLS 9 #define DMS_DBCLSA 10 #define DMS_DBENV 11 #define DMS_DBOPN 12 #define DMS_DBOPNA 13 #define DMS_DBSAVE 14 #define DMS_DBSTAT 15 #define DMS_DRC 16 #define DMS_DRM 17 #define DMS_DRO 18 #define DMS_FDRK 19 #define DMS_FFM 20 #define DMS_FFO 21 #define DMS_FFS 22 #define DMS_FLM 23 #define DMS_FLO 24 #define DMS_FMI 25 #define DMS_FMSK 26 #define DMS_FNM 27 #define DMS_FNMI 28 #define DMS_FNMSK 29 #define DMS_FNO 30 #define DMS_FNOI 31 #define DMS_FNOSK 32 #define DMS_FNS 33 #define DMS_FOI 34 #define DMS_FOSK 35 #define DMS_FPM 36 #define DMS_FPO 37 #define DMS_FRK 38 #define DMS_DBGETC 39 #define DMS_GETM 40 #define DMS_GETO 41 #define DMS_GFC 42 #define DMS_GFM 43 #define DMS_GFO 44 #define DMS_GMC 45 #define DMS_GOC 46 #define DMS_GTC 47 #define DMS_GTM 48 #define DMS_GTO 49 #define DMS_IMS 50 #define DMS_IOS 51 #define DMS_MAU 52 #define DMS_MCC 53 #define DMS_LGFILE 54 #define DMS_LGFLSH 55 #define DMS_LGMSG 56 #define DMS_NCI 57 #define DMS_PFC 58 #define DMS_PFM 59 #define DMS_PFO 60 #define DMS_PIFD 61 #define DMS_DBPUTC 62 #define DMS_PUTM 63 #define DMS_PUTO 64 #define DMS_RMS 65 #define DMS_ROS 66 #define DMS_RSM 67 #define DMS_RSO 68 #define DMS_SCM 69 #define DMS_SCN 70 #define DMS_SCO 71 #define DMS_SCU 72 //NULL, 73 NULL, NULL, #define DMS_SMC 74 #define DMS_SME 75 #define DMS_SMM 76 #define DMS_SMN 77 #define DMS_SMO 78 #define DMS_SMU 79 #define DMS_SOC 80 #define DMS_SOE 81 #define DMS_SOM 82 #define DMS_SON 83 #define DMS_SOO 84 #define DMS_SOU 85 #define DMS_SUC 86 #define DMS_SUM 87 #define DMS_SUN 88 #define DMS_SUO 89 #define DMS_SUU 90 #define DMS_TCT 91 #define DMS_TMT 92 #define DMS_TOT 93 #define DMS_TRABT 94 #define DMS_TRBGN 95 #define DMS_TRCOM 96 #define DMS_XMM 97 #define DMS_XMO 98 #define DMS_XOM 99 #define DMS_XOO 100 #define DMS_MCF 101 #define DMS_MCP 102 #define DMS_MRTF 103 #define DMS_MRTP 104 #define DMS_MSF 105 #define DMS_MSP 106 #define DMS_TCN 107 #define DMS_TMN 108 #define DMS_TON 109 #define DMS_DBCNV 110 #define DMS_ALTEOS 111 //NULL, 112 NULL, NULL, //NULL, 113 NULL, NULL, //NULL, 114 NULL, NULL, //NULL, 115 NULL, NULL, //NULL, 116 NULL, NULL, //NULL, 117 NULL, NULL, //NULL, 118 NULL, NULL, #define DMS_TUN 119 #define DMS_FPMI 120 #define DMS_FPOI 121 #define DMS_FPMSK 122 #define DMS_FPOSK 123 #define DMS_OFM 124 #define DMS_OFO 125 #define DMS_OLM 126 #define DMS_OLO 127 #define DMS_OMSK 128 #define DMS_ONM 129 #define DMS_ONMSK 130 #define DMS_ONO 131 #define DMS_ONOSK 132 #define DMS_OOSK 133 #define DMS_OPM 134 #define DMS_OPO 135 #define DMS_ODRK 136 #define DMS_ORK 137 #define DMS_OPMSK 138 #define DMS_OPOSK 139 #define DMS_OMI 140 #define DMS_ONMI 141 #define DMS_OPMI 142 #define DMS_OOI 143 #define DMS_ONOI 144 #define DMS_OPOI 145 #define DMS_SDC 146 #define DMS_SCD 147 #define DMS_MRID 148 #define DMS_GDC 149 #define DMS_GVLC 150 #define DMS_GII 151 #define DMS_GKI 152 #define DMS_GRI 153 #define DMS_GLRV 154 #define DMS_GHRV 155 #define DMS_GSI 156 #define DMS_GTT 157 //NULL, 158 NULL, NULL, //NULL, 159 NULL, NULL, #define DMS_GFLI 160 #define DMS_GAI 161 //NULL, 162 NULL, NULL, //NULL, 163 NULL, NULL, //NULL, 164 NULL, NULL, //NULL, 165 NULL, NULL, #define DMS_GAFN 166 //NULL, 167 NULL, NULL, #define DMS_TRSYNC 168 #define DMS_MCM 169 #define DMS_MRTM 170 #define DMS_MSM 171 #define DMS_MMU 172 #define DMS_NAL 173 #define DMS_FRM 174 #define DMS_FRO 175 #define DMS_GFS 176 #define DMS_DBXPND 177 #define DMS_GEI 178 #define DMS_DBSEL 179 #define DMS_VRDEF 180 #define DMS_VROF 181 #define DMS_VRON 182 #define DMS_GVII 183 #define DMS_GVRI 184 #define DMS_VRDEL 185 #define DMS_DBMEMU 186 #define DMS_TRBGNA 187 //NULL, 188 NULL, NULL, //NULL, 189 NULL, NULL, //NULL, 190 NULL, NULL, //"GKV", gkv, #define DMS_DMSSJP 191 #define DMS_DBFILE 192 #define DMS_GXERR 193 #define DMS_GXSTR 194 #define DMS_DBALIGN 195 #define DMS_DBAU 196 #define DMS_DBFU 197 #define DMS_DBSU 198 #define DMS_DBAVAIL 199 #define DMS_DBSRV 200 #define DMS_DBSTART 201 #define DMS_DBSTOP 202 #define DMS_DBUSR 203 //NULL, 204 NULL, NULL, //"SDM", scm, //NULL, 205 NULL, NULL, //"SDO", sco, //NULL, 206 NULL, NULL, //"SMD", smd, //NULL, 207 NULL, NULL, //"SOD", sod, #define DMS_DBEMS 208 #define DMS_FDB 209 #define DMS_IDB 210

#define amm(a) dms_call(NULL, a, DMS_AMM, sourcefilename, __LINE__) #define amo(a) dms_call(NULL, a, DMS_AMO, sourcefilename, __LINE__) #define aom(a) dms_call(NULL, a, DMS_AOM, sourcefilename, __LINE__) #define aoo(a) dms_call(NULL, a, DMS_AOO, sourcefilename, __LINE__) #define aui(a) dms_call(NULL, a, DMS_AUI, sourcefilename, __LINE__) #define ccu(a) dms_call(NULL, a, DMS_CCU, sourcefilename, __LINE__) #define cra(a,b) dms_call(a, b, DMS_CRA, sourcefilename, __LINE__) #define crs(a,b) dms_call(a, b, DMS_CRS, sourcefilename, __LINE__) #define dbcls() dms_call(NULL, NULL, DMS_DBCLS, sourcefilename, __LINE__) #define dbclsa(a) dms_call(NULL, a, DMS_DBCLSA, sourcefilename, __LINE__) #define dbenv(a) dms_call(NULL, a, DMS_DBENV, sourcefilename, __LINE__) #define dbopn(a) dms_call(NULL, a, DMS_DBOPN, sourcefilename, __LINE__) #define dbopna(a,b)dms_call(a, b, DMS_DBOPNA, sourcefilename, __LINE__) #define dbsave() dms_call(NULL, NULL, DMS_DBSAVE, sourcefilename, __LINE__) #define dbstat(a) dms_call(NULL, a, DMS_DBSTAT, sourcefilename, __LINE__) #define drc() dms_call(NULL, NULL, DMS_DRC, sourcefilename, __LINE__) #define drm(a) dms_call(NULL, a, DMS_DRM, sourcefilename, __LINE__) #define dro(a) dms_call(NULL, a, DMS_DRO, sourcefilename, __LINE__) #define fdrk(a,b) dms_call(a, b, DMS_FDRK, sourcefilename, __LINE__) #define ffm(a) dms_call(NULL, a, DMS_FFM, sourcefilename, __LINE__) #define ffo(a) dms_call(NULL, a, DMS_FFO, sourcefilename, __LINE__) #define ffs(a) dms_call(NULL, a, DMS_FFS, sourcefilename, __LINE__) #define flm(a) dms_call(NULL, a, DMS_FLM, sourcefilename, __LINE__) #define flo(a) dms_call(NULL, a, DMS_FLO, sourcefilename, __LINE__) #define fmi(a,b) dms_call(a, b, DMS_FMI, sourcefilename, __LINE__) #define fmsk(a,b) dms_call(a, b, DMS_FMSK, sourcefilename, __LINE__) #define fnm(a) dms_call(NULL, a, DMS_FNM, sourcefilename, __LINE__) #define fnmi(a,b) dms_call(a, b, DMS_FNMI, sourcefilename, __LINE__) #define fnmsk(a,b) dms_call(a, b, DMS_FNMSK, sourcefilename, __LINE__) #define fno(a) dms_call(NULL, a, DMS_FNO, sourcefilename, __LINE__) #define fnoi(a,b) dms_call(a, b, DMS_FNOI, sourcefilename, __LINE__) #define fnosk(a,b) dms_call(a, b, DMS_FNOSK, sourcefilename, __LINE__) #define fns(a) dms_call(NULL, a, DMS_FNS, sourcefilename, __LINE__) #define foi(a,b) dms_call(a, b, DMS_FOI, sourcefilename, __LINE__) #define fosk(a,b) dms_call(a, b, DMS_FOSK, sourcefilename, __LINE__) #define fpm(a) dms_call(NULL, a, DMS_FPM, sourcefilename, __LINE__) #define fpo(a) dms_call(NULL, a, DMS_FPO, sourcefilename, __LINE__) #define frk(a,b) dms_call(a, b, DMS_FRK, sourcefilename, __LINE__) #define dbgetc(a) dms_call(NULL, a, DMS_DBGETC, sourcefilename, __LINE__) #define getm(a,b) dms_call(a, b, DMS_GETM, sourcefilename, __LINE__) #define geto(a,b) dms_call(a, b, DMS_GETO, sourcefilename, __LINE__) #define gfc(a,b) dms_call(a, b, DMS_GFC, sourcefilename, __LINE__) #define gfm(a,b) dms_call(a, b, DMS_GFM, sourcefilename, __LINE__) #define gfo(a,b) dms_call(a, b, DMS_GFO, sourcefilename, __LINE__) #define gmc(a,b) dms_call(a, b, DMS_GMC, sourcefilename, __LINE__) #define goc(a,b) dms_call(a, b, DMS_GOC, sourcefilename, __LINE__) #define gtc(a) dms_call(NULL, a, DMS_GTC, sourcefilename, __LINE__) #define gtm(a,b) dms_call(a, b, DMS_GTM, sourcefilename, __LINE__) #define gto(a,b) dms_call(a, b, DMS_GTO, sourcefilename, __LINE__) #define ims(a) dms_call(NULL, a, DMS_IMS, sourcefilename, __LINE__) #define ios(a) dms_call(NULL, a, DMS_IOS, sourcefilename, __LINE__) #define mau(a) dms_call(NULL, a, DMS_MAU, sourcefilename, __LINE__) #define mcc(a) dms_call(NULL, a, DMS_MCC, sourcefilename, __LINE__) #define lgfile(a) dms_call(NULL, a, DMS_LGFILE, sourcefilename, __LINE__) #define lgflsh() dms_call(NULL, NULL, DMS_LGFLSH, sourcefilename, __LINE__) #define lgmsg(a) dms_call(NULL, a, DMS_LGMSG, sourcefilename, __LINE__) #define nci() dms_call(NULL, NULL, DMS_NCI, sourcefilename, __LINE__) #define pfc(a,b) dms_call(a, b, DMS_PFC, sourcefilename, __LINE__) #define pfm(a,b) dms_call(a, b, DMS_PFM, sourcefilename, __LINE__) #define pfo(a,b) dms_call(a, b, DMS_PFO, sourcefilename, __LINE__) #define pifd(a) dms_call(NULL, a, DMS_PIFD, sourcefilename, __LINE__) #define dbputc(a) dms_call(NULL, a, DMS_DBPUTC, sourcefilename, __LINE__) #define putm(a,b) dms_call(a, b, DMS_PUTM, sourcefilename, __LINE__) #define puto(a,b) dms_call(a, b, DMS_PUTO, sourcefilename, __LINE__) #define rms(a) dms_call(NULL, a, DMS_RMS, sourcefilename, __LINE__) #define ros(a) dms_call(NULL, a, DMS_ROS, sourcefilename, __LINE__) #define rsm(a) dms_call(NULL, a, DMS_RSM, sourcefilename, __LINE__) #define rso(a) dms_call(NULL, a, DMS_RSO, sourcefilename, __LINE__) #define scm(a) dms_call(NULL, a, DMS_SCM, sourcefilename, __LINE__) #define scn() dms_call(NULL, NULL, DMS_SCN, sourcefilename, __LINE__) #define sco(a) dms_call(NULL, a, DMS_SCO, sourcefilename, __LINE__) #define scu(a) dms_call(NULL, a, DMS_SCU, sourcefilename, __LINE__) #define smc(a) dms_call(NULL, a, DMS_SMC, sourcefilename, __LINE__) #define sme(a) dms_call(NULL, a, DMS_SME, sourcefilename, __LINE__) #define smm(a) dms_call(NULL, a, DMS_SMM, sourcefilename, __LINE__) #define smn(a) dms_call(NULL, a, DMS_SMN, sourcefilename, __LINE__) #define smo(a) dms_call(NULL, a, DMS_SMO, sourcefilename, __LINE__) #define smu(a,b) dms_call(a, b, DMS_SMU, sourcefilename, __LINE__) #define soc(a) dms_call(NULL, a, DMS_SOC, sourcefilename, __LINE__) #define soe(a) dms_call(NULL, a, DMS_SOE, sourcefilename, __LINE__) #define som(a) dms_call(NULL, a, DMS_SOM, sourcefilename, __LINE__) #define son(a) dms_call(NULL, a, DMS_SON, sourcefilename, __LINE__) #define soo(a) dms_call(NULL, a, DMS_SOO, sourcefilename, __LINE__) #define sou(a,b) dms_call(a, b, DMS_SOU, sourcefilename, __LINE__) #define suc(a) dms_call(NULL, a, DMS_SUC, sourcefilename, __LINE__) #define sum(a,b) dms_call(a, b, DMS_SUM, sourcefilename, __LINE__) #define sun(a) dms_call(NULL, a, DMS_SUN, sourcefilename, __LINE__) #define suo(a,b) dms_call(a, b, DMS_SUO, sourcefilename, __LINE__) #define suu(a) dms_call(NULL, a, DMS_SUU, sourcefilename, __LINE__) #define tct(a) dms_call(NULL, a, DMS_TCT, sourcefilename, __LINE__) #define tmt(a) dms_call(NULL, a, DMS_TMT, sourcefilename, __LINE__) #define tot(a) dms_call(NULL, a, DMS_TOT, sourcefilename, __LINE__) #define trabt() dms_call(NULL, NULL, DMS_TRABT, sourcefilename, __LINE__) #define trbgn() dms_call(NULL, NULL, DMS_TRBGN, sourcefilename, __LINE__) #define trcom() dms_call(NULL, NULL, DMS_TRCOM, sourcefilename, __LINE__) #define xmm(a) dms_call(NULL, a, DMS_XMM, sourcefilename, __LINE__) #define xmo(a) dms_call(NULL, a, DMS_XMO, sourcefilename, __LINE__) #define xom(a) dms_call(NULL, a, DMS_XOM, sourcefilename, __LINE__) #define xoo(a) dms_call(NULL, a, DMS_XOO, sourcefilename, __LINE__) #define mcf() dms_call(NULL, NULL, DMS_MCF, sourcefilename, __LINE__) #define mcp() dms_call(NULL, NULL, DMS_MCP, sourcefilename, __LINE__) #define mrtf(a) dms_call(NULL, a, DMS_MRTF, sourcefilename, __LINE__) #define mrtp(a) dms_call(NULL, a, DMS_MRTP, sourcefilename, __LINE__) #define msf(a) dms_call(NULL, a, DMS_MSF, sourcefilename, __LINE__) #define msp(a) dms_call(NULL, a, DMS_MSP, sourcefilename, __LINE__) #define tcn() dms_call(NULL, NULL, DMS_TCN, sourcefilename, __LINE__) #define tmn(a) dms_call(NULL, a, DMS_TMN, sourcefilename, __LINE__) #define ton(a) dms_call(NULL, a, DMS_TON, sourcefilename, __LINE__) #define dbcnv(a) dms_call(NULL, a, DMS_DBCNV, sourcefilename, __LINE__) #define alteos() dms_call(NULL, NULL, DMS_ALTEOS, sourcefilename, __LINE__) #define tun(a) dms_call(NULL, a, DMS_TUN, sourcefilename, __LINE__) #define fpmi(a,b) dms_call(a, b, DMS_FPMI, sourcefilename, __LINE__) #define fpoi(a,b) dms_call(a, b, DMS_FPOI, sourcefilename, __LINE__) #define fpmsk(a,b) dms_call(a, b, DMS_FPMSK, sourcefilename, __LINE__) #define fposk(a,b) dms_call(a, b, DMS_FPOSK, sourcefilename, __LINE__) #define ofm(a,b) dms_call(a, b, DMS_OFM, sourcefilename, __LINE__) #define ofo(a,b) dms_call(a, b, DMS_OFO, sourcefilename, __LINE__) #define olm(a,b) dms_call(a, b, DMS_OLM, sourcefilename, __LINE__) #define olo(a,b) dms_call(a, b, DMS_OLO, sourcefilename, __LINE__) #define omsk(a,b) dms_call(a, b, DMS_OMSK, sourcefilename, __LINE__) #define onm(a,b) dms_call(a, b, DMS_ONM, sourcefilename, __LINE__) #define onmsk(a,b) dms_call(a, b, DMS_ONMSK, sourcefilename, __LINE__) #define ono(a,b) dms_call(a, b, DMS_ONO, sourcefilename, __LINE__) #define onosk(a,b) dms_call(a, b, DMS_ONOSK, sourcefilename, __LINE__) #define oosk(a,b) dms_call(a, b, DMS_OOSK, sourcefilename, __LINE__) #define opm(a,b) dms_call(a, b, DMS_OPM, sourcefilename, __LINE__) #define opo(a,b) dms_call(a, b, DMS_OPO, sourcefilename, __LINE__) #define odrk(a,b) dms_call(a, b, DMS_ODRK, sourcefilename, __LINE__) #define ork(a,b) dms_call(a, b, DMS_ORK, sourcefilename, __LINE__) #define opmsk(a,b) dms_call(a, b, DMS_OPMSK, sourcefilename, __LINE__) #define oposk(a,b) dms_call(a, b, DMS_OPOSK, sourcefilename, __LINE__) #define omi(a,b) dms_call(a, b, DMS_OMI, sourcefilename, __LINE__) #define onmi(a,b) dms_call(a, b, DMS_ONMI, sourcefilename, __LINE__) #define opmi(a,b) dms_call(a, b, DMS_OPMI, sourcefilename, __LINE__) #define ooi(a,b) dms_call(a, b, DMS_OOI, sourcefilename, __LINE__) #define onoi(a,b) dms_call(a, b, DMS_ONOI, sourcefilename, __LINE__) #define opoi(a,b) dms_call(a, b, DMS_OPOI, sourcefilename, __LINE__) #define sdc(a) dms_call(NULL, a, DMS_SDC, sourcefilename, __LINE__) #define scd(a) dms_call(NULL, a, DMS_SCD, sourcefilename, __LINE__) #define mrid(a) dms_call(NULL, a, DMS_MRID, sourcefilename, __LINE__) #define gdc(a) dms_call(NULL, a, DMS_GDC, sourcefilename, __LINE__) #define gvlc(a,b) dms_call(a, b, DMS_GVLC, sourcefilename, __LINE__) #define gii(a,b) dms_call(a, b, DMS_GII, sourcefilename, __LINE__) #define gki(a,b) dms_call(a, b, DMS_GKI, sourcefilename, __LINE__) #define gri(a,b) dms_call(a, b, DMS_GRI, sourcefilename, __LINE__) #define glrv(a,b) dms_call(a, b, DMS_GLRV, sourcefilename, __LINE__) #define ghrv(a,b) dms_call(a, b, DMS_GHRV, sourcefilename, __LINE__) #define gsi(a,b) dms_call(a, b, DMS_GSI, sourcefilename, __LINE__) #define gtt(a,b) dms_call(a, b, DMS_GTT, sourcefilename, __LINE__) #define gfli(a) dms_call(NULL, a, DMS_GFLI, sourcefilename, __LINE__) #define gai(a,b) dms_call(a, b, DMS_GAI, sourcefilename, __LINE__) #define gafn(a,b) dms_call(a, b, DMS_GAFN, sourcefilename, __LINE__) #define trsync(a) dms_call(NULL, a, DMS_TRSYNC, sourcefilename, __LINE__) #define mcm() dms_call(NULL, NULL, DMS_MCM, sourcefilename, __LINE__) #define mrtm(a) dms_call(NULL, a, DMS_MRTM, sourcefilename, __LINE__) #define msm(a) dms_call(NULL, a, DMS_MSM, sourcefilename, __LINE__) #define mmu(a) dms_call(NULL, a, DMS_MMU, sourcefilename, __LINE__) #define nal() dms_call(NULL, NULL, DMS_NAL, sourcefilename, __LINE__) #define frm(a,b) dms_call(a, b, DMS_FRM, sourcefilename, __LINE__) #define fro(a,b) dms_call(a, b, DMS_FRO, sourcefilename, __LINE__) #define gfs(a,b) dms_call(a, b, DMS_GFS, sourcefilename, __LINE__) #define dbxpnd(a,b)dms_call(a, b, DMS_DBXPND, sourcefilename, __LINE__) #define gei(a) dms_call(NULL, a, DMS_GEI, sourcefilename, __LINE__) #define dbsel(a) dms_call(NULL, a, DMS_DBSEL, sourcefilename, __LINE__) #define vrdef(a,b) dms_call(a, b, DMS_VRDEF, sourcefilename, __LINE__) #define vrof(a,b) dms_call(a, b, DMS_VROF, sourcefilename, __LINE__) #define vron(a,b) dms_call(a, b, DMS_VRON, sourcefilename, __LINE__) #define gvii(a,b) dms_call(a, b, DMS_GVII, sourcefilename, __LINE__) #define gvri(a,b) dms_call(a, b, DMS_GVRI, sourcefilename, __LINE__) #define vrdel(a) dms_call(NULL, a, DMS_VRDEL, sourcefilename, __LINE__) #define dbmemu(a) dms_call(NULL, a, DMS_DBMEMU, sourcefilename, __LINE__) #define trbgna() dms_call(NULL, NULL, DMS_TRBGNA, sourcefilename, __LINE__) #define dmssjp(a,b)dms_call(a, b, DMS_DMSSJP, sourcefilename, __LINE__) #define dbfile(a) dms_call(NULL, a, DMS_DBFILE, sourcefilename, __LINE__) #define gxerr(a) dms_call(NULL, a, DMS_GXERR, sourcefilename, __LINE__) #define gxstr(a) dms_call(NULL, a, DMS_GXSTR, sourcefilename, __LINE__) #define dbalign() dms_call(NULL, NULL, DMS_DBALIGN,sourcefilename, __LINE__) #define dbau() dms_call(NULL, NULL, DMS_DBAU, sourcefilename, __LINE__) #define dbsu(a) dms_call(NULL, a, DMS_DBSU, sourcefilename, __LINE__) #define dbfu() dms_call(NULL, NULL, DMS_DBFU, sourcefilename, __LINE__) #define dbavail(a) dms_call(NULL, a, DMS_DBAVAIL,sourcefilename, __LINE__) #define dbsrv(a) dms_call(NULL, a, DMS_DBSRV, sourcefilename, __LINE__) #define dbstart(a) dms_call(NULL, a, DMS_DBSTART,sourcefilename, __LINE__) #define dbstop(a) dms_call(NULL, a, DMS_DBSTOP, sourcefilename, __LINE__) #define dbusr(a,b) dms_call(a, b, DMS_DBUSR, sourcefilename, __LINE__) #define dbems(a) dms_call(NULL, a, DMS_DBEMS, sourcefilename, __LINE__) /* Eof. DMSFUNC.H */

Back to Article

Listing Two

/*************************** Module Name: DMSCALL.c
**************************/
#define NTC_DMS_FILENAME
static char sourcefilename[] = "DMSCALL";


#include "mmsys.h" #include "codedef.h"

/* ** The next two constants define the number of times the error-64-handler ** will retry the call before asking the user whether to continue, and ** the number of 10ths of a second between retries. */ #define MAX_ERR64_RETRIES 15 #define ERR64_RETRY_INTERVAL 20

#define DOT_SYMBOL '+'

extern short dmserrs[]; void dms_error(int, int, char *, unsigned int, BOOL, BOOL);

/****************************** * DESCRIPTION: * This function replaces all calls to MDBS functions. Macros redefine * the DMS calls to call this function instead. * For example, * #define crs(a,b) dms_call(crs,(char *)a,(char *)b,8,__FILE__,__LINE__) * the argument fnum is MDBS's unique number for the function. * filename and linenumb are the name of the file and the line number as * determined by the preprocessor and set as replacements for __FILE__ and * __LINE__, respectively. * Benign command status values are returned to the calling function. More * serious values are given to dms_error() which displays the information * and exits to DOS. ******************************/ int NTC_PASCAL dms_call (arg1, arg2, fnum, filename, linenumb) char *arg1, *arg2; int fnum; char *filename; unsigned int linenumb; { int error, nwindow; int i, ntries; BOOL dbbusy_window_open; char dots_array[80]; int last_dot; char context_buffer[80], logmsg[100]; struct lock_msg_struct msgbuf; static char context_fmtstr[] = "(status: %d, fn: %d, file: %s %u)"; MSG msg; /* ** ntries is set at MAX_ERR64_RETRIES, in the case that an error 64 ** occurs. If it is error 64, ntries will be decremented each time ** the function is retried. */ ntries = MAX_ERR64_RETRIES; /* ** dbbusy_window_open is used to track whether the "DB Busy" window ** has been opened or not. */ dbbusy_window_open = FALSE; /* ** last_dot is an index to the array of dots which increments with each ** retry. Used with error 64. */ last_dot = 0; /* ** nwindow indicates how many windows are opened. ** It has to be initialized here because in the case of error 64 it ** is necessary to close the DB BUSY window if that window is open and on ** the retry an error other than 64 occurs. ** In this case it is necessary that we ** close no windows if the error 64 case has not opened any. */ nwindow = 0;

do { /* ** Call the DMS function, and return immediately if ** the command status is 0. */ error = (*(get_dmsfptr(fnum)))(arg1, arg2); /* ** See note for nwindow above. */ if ((nwindow) && (error != 64)) { ntc_close_window (nwindow, &nwindow); spi_flush_screen(); } if (error == 0) { /* ** Close DB Busy window if it is open. */ if (dbbusy_window_open) { ntc_close_window (nwindow, &nwindow); spi_flush_screen(); } return (0); } /* ** Check the array of allowable errors. Return if the non-zero ** error from *fptr is in the array; else go to the switch below. ** The variable pass_array is a global array. */ for (i = 0; dmserrs[i]; ++i) { if (error == dmserrs[i]) { if ((dbbusy_window_open)) { ntc_close_window (nwindow, &nwindow); spi_flush_screen(); } return (error); } } switch (error) { case 255: /* ** End-of-set condition. This is not really an error at all. ** Return the end-of-set result code to the caller. */ if (dbbusy_window_open) { ntc_close_window (nwindow, &nwindow); spi_flush_screen(); } return(255);

case 62: case 63: case 68: /* ** Record-locking conflict */ clearstruct(msgbuf); sprintf(msgbuf.msg[0], "Status: %d fn: %d File: %s Line: %d", error, fnum, filename, linenumb);

/* Give user option of retrying or aborting */ switch (retry_dms_msg("xusertry", &msgbuf)) { case RETRY_YES: break; case RETRY_NO: dms_error(error, fnum, filename, linenumb, FALSE, TRUE); break; // dms_error should never return. } break; case 64: /* ** Our operation was rejected because another user has an ** abortable transaction in progress. */ if (! dbbusy_window_open) { ntc_open_std_window ("xdbbusy", 5, 13, &nwindow); spi_string_tofield_byname("fil", filename); spi_int_tofield_byname("lin", linenumb); spi_flush_screen(); dbbusy_window_open = TRUE; } else { /* ** If the xdbbusy window is already open, write a dot to ** the window. */ dots_array[last_dot] = DOT_SYMBOL; dots_array[++last_dot] = '\0'; spi_string_tofield_byname("dbbusy", dots_array); spi_flush_screen(); } spi_keyhit(ERR64_RETRY_INTERVAL); // Wait 2 seconds. if (ntries--) continue; last_dot = 0; *dots_array = '\0'; sprintf (context_buffer, context_fmtstr, error, fnum, filename, linenumb); switch (retry_dms ("xdb64ask", context_buffer)) { case RETRY_YES: ntries = MAX_ERR64_RETRIES; continue; case RETRY_NO: dms_error(error, fnum, filename, linenumb, FALSE, TRUE); break; // dms_error should never return. } break; default: if (dbbusy_window_open) { ntc_close_window (nwindow, &nwindow); spi_flush_screen(); } dms_error(error, fnum, filename, linenumb, TRUE, TRUE); break; /* dms_error should never return. */ } /* end of switch (error) */ } while (TRUE); /* end of while; will always loop again */ }

Back to Article

Listing Three

/******************* Insert the current record into the specified set.
* Handles the case where the record is already a member of the set.
* Return Values:
*    0  Record inserted into set successfully.
*    1  Record was already a member of the set.
******************/
void InsertRecord(char *setname)
{
    int ret;
    passerr(11);         /* Tell the DMS error handler not to trap error 11 */
    ret = ims(setname);  /* Attempt insertion of record into set */
    passerr(-11);        /* Restore default trapping of DMS Error 11 */
    return((ret == 11) ? 1 : 0);
}


Back to Article

Dr. Dobb's Sourcebook July/August 1997: Titanium and the NTC Ship Manager Application

Titanium and the NTC Ship Manager Application

By Evan Michaelides

Dr. Dobb's Sourcebook July/August 1997

Figure 1: Original recursive set and the additional redundant set linking the equipment record directly to all parts.

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