C Programming

Al analyzes the C++ Standard Template Library (STL), then wraps up his text-search project by discussing the C source-code module of the interface between the Visual Basic front end and the Windows DLL that implements the search engine.


April 01, 1995
URL:http://www.drdobbs.com/cpp/c-programming/184409540

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

APR95: C PROGRAMMING

C PROGRAMMING

The Standard Template Library, Visual C++ Training, Text-Search Wrap-up

Al Stevens

Last month I interviewed Alexander Stepanov, whose Standard Template Library (STL) was accepted in 1994 as a major part of the Standard C++ library. STL is a library of container-class templates and algorithmic-function templates. This discussion (which I adapted from the last chapter of my book Teach Yourself C++, Fourth Edition) provides an overview of STL.

STL's rationale is found in its generic-programming model. Given one set of data types, another set of container types, and a third set of algorithms, the amount of software to be developed with traditional, nongeneric C++ methods is a product of the number of elements in the three sets. If you have integer, Date, and Personnel objects to contain in lists, queues, and stacks, and you need insert, extract, and sort algorithms for each, then there are 27 (3x3x3) traditional C++ algorithms to develop. With traditional templates, you can define the containers as generic classes and reduce the number to nine algorithms: three algorithms for each of the three containers. If, however, you design the algorithms as templates that perform generic operations on parameterized containers, then there are only three algorithms to write, and that orthogonal organization is the underlying basis for STL's generic-programming model.

That argument is a simplification of the STL rationale, but it hints at larger advantages, ones that cannot be ignored. First, if class template containers are sufficiently generic, they can support any user-defined data type that meets their requirements with respect to operator overloading and behavior. You can contain any data type within any kind of supported container without having to develop custom container code. Second, if the algorithms are sufficiently generic, you can use them to process containers of objects of user-defined data types. Third, you introduce new containers by conforming to the rules of STL. The existing algorithms automatically work with the new containers. Finally, conforming new algorithms work with all present containers and contained data types.

If you stick to the rules, you can add to any of the three components that comprise STL--the containers, the algorithms, and the contained data types--and all existing components will automatically accept the new addition and work seamlessly with it.

The Standard Containers

STL supports several container types cate-gorized as sequences and associative containers. Access to containers is managed by a hierarchy of iterator objects that resemble C++ pointers. Iterators point to objects in the containers and permit the program to iterate through the containers in various ways.

The containers all have common management member functions defined in their template definitions: insert, erase, begin, end, size, capacity, and so on. Individual containers have member functions that support their unique requirements.

A standard suite of algorithms provides for searching, copying, reordering, transforming, and performing numeric operations on the objects in the containers. The same algorithm is used to perform a particular operation for all containers of all object types.

Sequences

A sequence is a container that stores a finite set of objects of the same type in a linear organization. An array of names is a sequence. You would use one of the three sequence types--vector, list, or deque--for a particular application, depending on its retrieval requirements.

A vector is a sequence that you can access at random. You can append entries to and remove entries from the end of the vector without undue overhead. Insertion and deletion at the beginning or in the middle of the vector take more time because they involve shifting the remaining entries to make room or to close up the deleted object space. A vector is an array of contiguous objects with an instance counter or pointer to indicate the end of the container. Random access is a matter of using a subscript operation.

A list is a sequence that you access bidirectionally and perform inserts and deletes anywhere without undue performance penalties. Random access requires forward or backward iteration to the target object. A list consists of noncontiguous objects linked together with forward and backward pointers.

A deque is like a vector, except that it allows fast inserts and deletes at the beginning as well as the end of the container. Random inserts and deletes take more time.

Associative Containers

Associative containers provide for fast, keyed access to the objects in the container. They are constructed from key objects and a compare function that the container uses to compare objects. Associative containers consist of set, multiset, map, and multimap containers. You would use associative containers for large dynamic tables that you can search sequentially or at random. Associative containers use tree structures to organize the objects rather than contiguous arrays or linked lists. These structures support fast random retrievals and updates.

The set and multiset containers contain objects that are key values. The set container does not permit multiple keys with the same value; the multiset container does.

The map and multimap containers contain objects that are key values. They associate each key object with another parameterized type object. The map container does not permit multiple keys with the same value; the multimap container does.

Iterators

Iterators provide a common method of access into containers. They resemble and have the semantics of C++ pointers. In fact, when the parameterized type is a built-in C++ type (int, double, and so on), the associated iterators are C++ pointers.

Each container type supports one category of iterator depending on the container's requirements. The categories are: Input, Output, Forward, Bidirectional, and Random Access. STL defines a hierarchy of iterators, as shown in Figure 1.

Each iterator category has all the properties of those above it in the hierarchy. Those properties specify the behavior that the iterator must exhibit in order to support the container. Iterators are "smart" pointers. They are permitted to have values that represent one of a set of defined states. These states are listed and explained in Table 1.

Iterators can be initialized, incremented, and decremented, and their bounds can be limited by the current extent of the containers. If you can cause one iterator to be equal to another by incrementing the first, the second iterator is reachable from the first. The two iterators are also known to refer to the same container and can therefore define a range of objects in the container.

Iterators can be set as the result of a search of the container or by subscripted reference into the container. Containers include member functions that return iterators that point to the first object and the past-the-end object position. Iterators are the objects with which STL algorithms work.

Algorithms

Algorithms perform operations on containers by dereferencing iterators. Each algorithm is a function template parameterized on one or more iterator types. Algorithms are the backbone of STL. Table 2 lists the standard algorithms provided with STL.

Algorithms accept iterators as arguments. The iterators tell the algorithm on which object or range of objects in a container to operate.

To build and sort a vector container of pseudorandom integers, you could use the program in Listing One which instantiates a vector container of integer objects named "vct." Then it uses the vector template-class member function insert to insert random numbers into the vector container. It displays the container's contents, retrieving each of the objects by using the overloaded [] operator. Next, the program uses the STL sort algorithm to sort the container's contents. The sort function accepts a range of objects expressed as a pair of iterators. The second iterator must be reachable from the first. The container begin and end member functions return iterators that refer to the first object in the container and the past-the-end position of the container. This pair constitutes a range that represents the entire container.

Not every algorithm works with every container. The hierarchy of iterator types maintains this relationship. It is inefficient, and therefore illegal, to apply some algorithms to some data structures. The association of a container with its supporting algorithms is controlled by the container's iterator types. You cannot, for example, use the sort algorithm, which requires random iterators, to sort a list, which uses bidirectional iterators. The sort algorithm expects iterators to point to memory-adjacent objects so that it can rearrange the objects in a contiguous array. Iterators for lists do not have that property. Each one points to an entry whose physical address is coincidental to the other objects in the list. Their logical juxtaposition in the list is represented by hidden list pointers instead of by a physical position in a contiguous array. The sort algorithm would be extremely inefficient if it tried to sort such a data structure. If you replace vector with list in the program in Listing One, two kinds of compiler errors occur. First, the [] operator does not work for iterating through and extracting objects. The list<T> container class does not overload the [] operator. You have to use iterators for that operation. The other errors occur deep down inside STL functions when STL tries to subtract and compare iterators by using operators that are not overloaded. Here you have to be somewhat familiar with the underlying library. The errors are typical C++ cryptic compiler messages relating to the parameterized usage that STL tries to instantiate. "Illegal structure operation," for example. The real clue is the error message:

Could not find a match for

'__insertion_sort(list<int>::iterator,

undefined)'

which tells you that you can't sort a list. It follows that if you want to invent a container that can be sorted with the generic sort algorithm, the container must include an iterator derived from the STL random-access iterator.

Predicates

Algorithms accept predicates, which are function-object arguments. A function object overloads operator(); you pass it to an algorithm as a callback-function argument. The algorithm calls the predicate for each object that it processes from the container. In some cases, the predicate is a bool function that returns true or false to tell the algorithm whether to select the object. In other cases, the predicate processes the objects that the algorithm finds and returns an object of the type in the container. STL provides a set of standard arithmetic, comparison, and logical function objects that you can use as predicates.

Allocators

Allocators are STL objects that encapsulate information about the container's storage medium, specifically, the compiler's memory models. Each container template uses an embedded allocator object to allocate memory for objects in the container. The behavior of the allocator object is parameterized for the type, and users can override the allocator object by defining their own.

In its current implementation, STL disables the _new_handler pointer. Then it displays a message on cout and calls exit(1) if memory is exhausted by an allocator. This behavior might not be appropriate for a program that manages its own memory, does not use standard console devices, or needs a more orderly exit to release interrupt vectors, for example. Current versions of STL do not throw exceptions, and the committee is looking into that. In the meantime, you can install custom allocators if you need different memory-exhaustion strategies.

STL Summarized

STL consists of generic containers with iterators and algorithms that operate on those containers through their iterators. STL is almost a different programming model--another paradigm, if you will. It differs from pure object-oriented theory by separating the data from the functions. Algorithms are not encapsulated in classes. They are not methods. They are function templates. Their binding to the data occurs as a function of their orthogonal relationship to their parameterized iterators.

The generic-programming model is an important paradigm, and it adds to, instead of replaces, what you already know. It will get a lot of publicity in the coming months. But it isn't a bandwagon, so don't start jumping prematurely. Just as object-oriented programming did not replace structured programming, generic programming does not offset object-oriented programming. Problem-domain class design still involves abstraction, encapsulation, polymorphism, and inheritance. Generic programming applies quite specifically to the design and development of container data structures, those collections that manage and hold objects of your object-oriented classes. Two different things.

You can experiment with STL by downloading it as an anonymous ftp from butler .hpl.hp.com under /stl and using Borland C++ 4.5 or IBM's CSet++ compiler for OS/2.

SD Training Intensives

STL's introduction of "generic programming" into our lexicon reminds me that programming keeps getting harder. It was never supposed to do that. They promised. Productivity tools are meant to increase productivity and make our lives easier. But each new language, paradigm, library, framework, tool, utility, and operating platform adds one more layer of complexity to the things that a programmer needs to know. It is not always certain that the degree of increased empowerment equals the effort and knowledge required to achieve that degree.

In December, I attended the Software Development Training Intensives classes conducted in Orlando by Bruce Eckel and Richard Hale Shaw. The Orlando sessions were part of a multicity, traveling road show, the details of which are advertised in DDJ and other Miller Freeman publications.

Bruce's two days are a total immersion into C++ for C programmers. Already knowing C++, I attended only the last part of that session. My attention turned to the audience. They were spellbound and a little glassy-eyed after two intensive days of nonstop, wall-to-wall, brain-soaking C++. Bruce uses his new book, Thinking in C++ (Prentice Hall, 1995) for the classroom textbook, and I spent some time later with that book. Bruce's treatment of iostreams is the most comprehensive I've seen in an introductory book.

Richard's two days are devoted to Windows programming with Visual C++ 2.0 and the Microsoft Foundation Classes, and I attended both days. I had already run the VC++ 2.0 tutorial at home and wanted to know more. Without a doubt, those two days were the most educational I've ever spent learning anything. Not only is Richard a master teacher, but the subject is compelling. With respect to my earlier concerns about programming getting harder, let me say here without reservation that the Visual C++ development environment empowers the programmer far beyond the effort required to climb the learning slope. The learning curve is steep, but you come out of it with a lot of development power at your fingertips. If you are about to plunge into Windows 95 or NT development and need to learn the fundamentals in a hurry, there is no better way to do it than to spend two days with Richard Hale Shaw.

The Text-Search Project

I'll wrap up the text-search project this month by discussing the C source-code module of the interface between the Visual Basic (VB) front end and the Windows DLL that implements the search engine. There are several other C source files in the engine to support query parsing, database retrievals, decompression, and so on. Most of that code comes from past column projects, so I won't discuss it again. All the source code is available in a configuration that builds this project. I'll tell you how to get it at the end of this discussion.

Listing Two, bibsrch.c, serves two purposes. I tested the engine as a stand-alone DOS program. The source code includes a compile-time directive that compiles either a Windows DLL or the stand-alone test program. The test program includes a stubbed text-mode front end to exercise all the functions. This approach lets me use the more-familiar DOS environment to test the program before it becomes a mysterious DLL. Later, when I know more about Windows programming, that approach will not be necessary.

The first part of the program is the stub, which includes a table of chapter names for the query responses. Next is a main function that opens the database and fires off a simple, menu-driven interactive session. Each menu selection calls one of the engine's interface functions and displays the result.

When you compile the source program to be included in the DLL, the stub code does not get compiled. Instead, the program compiles standard WinMain and WEP functions to handle the DLL's startup and exit code. There is also a Windows-specific RegisterAppl function that the VB front end calls every time the user runs the program. If a copy is already running, the function does not try to open the database again. The VB front end can tell from the return value that a copy of the program is already running. In that case, the program calls into the previously running copy and exits instead of trying to execute multiple copies.

Several utility functions compile for both the DOS and DLL versions of the program. The ComputeDocNo function converts book, chapter, and verse variables into a document number. The DocNotoBCV function converts them back. The ReadVerse and ReadVerseText functions read text for the current verse and any user-added annotations. Following that are three front-end interface functions--GetVerse, NextVerse, and PrevVerse--declared with type qualifiers FAR _export PASCAL. Those conventions identify DLL functions that outside programs can call. When the program is compiled to run under DOS, those tokens are #defined to a null string.

Each interface function has parameters that are far pointers to memory that will receive the text of the selected verse and any annotation. Other parameters are pointers to the book, chapter, and verse integer specifications. The front end passes these values to GetVerse by ensuring that the arguments point to valid values. The NextVerse and PrevVerse functions determine the values for themselves based on the next or previous logical document in the database. Then they return the values to the front end through the argument pointers. That way the front end does not need to keep track of how many verses are in every chapter and how many chapters are in every book. The SetRetrievalMode function tells the engine whether the user is currently navigating through verses or annotations.

Other front-end interface functions are in other source-code modules. Table 3 lists these functions. All of the functions may be assumed to have the FAR _export PASCAL qualifier.

The LexicalScan function initiates a query, parsing and processing the query. The function returns False if the query is invalid. In this case, the offset argument is set to the offset in the query expression where the syntax checker found the problem. If the expression is okay, the function copies the number of hits into the caller's memory. The phrase argument is True if the caller is requesting a phrase search rather than a Boolean search. The difference is that in the former case, all the words in the expression are assumed to be text words to be searched with And operators. The first pass of a phrase search builds a hit list of documents that contain all the words in the phrase without concern for whether the hit is a coincidence or actually contains the phrase.

When LexicalScan returns, the query retrieval is finished, and the DLL has a hit list to process. If the caller is processing a phrase search, a call to the SearchPhrase function tells the engine to scan the documents in the hit list to see if the phrase really exists.

With a hit list built, the front end calls NextFoundVerse successively to retrieve the book, chapter, and verse numbers for each of the documents in the hit list. The front end knows how many calls to make from the count of hits returned by the LexicalScan function. After retrieving all the document specifications from the hit list, the front end calls EndSearch to terminate the search. It is up to the front end to call GetVerse to retrieve the text and annotation of any particular verse. The Bible application displays a list of found verses in a list box and retrieves and displays the text of only the currently selected verse in the list box.

The engine keeps a record of the most-recently retrieved document so that subsequent calls to NextVerse and PrevVerse retrieve the correct document.

Source Code and Database

The database and Visual Basic source code for the Bible application and the C source code for the text engine are free. You can download them from the DDJ Forum on CompuServe, from DDJ Online, and via anonymous ftp; see "Availability," page 3.

If you cannot get to one of the online sources, send two high-density 3.5-inch diskettes and an addressed, stamped mailer to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402, and I'll send you the source code and database. It's free, but if you care to support my Careware charity, include a dollar for the Brevard County Food Bank.

Figure 1 STL iterator hierarchy.

Table 1: STL iterator states.

Iterator State    Meaning   

Singular          The iterator's value does not dereference any object in
                  any container.
                  (The iterator could be uninitialized or set to a logical
                  null value.)
Dereferenceable   The iterator points to a valid object in the container.
Past-the-end      The iterator points to the first adjacent object position
                  beyond the last object in the container.

Table 2: Standard STL algorithms.

Nonmutating     Mutating           Generalized                       
Sequence        Sequence           Sorting               Numeric     
Operations      Operations         Operations            Operations  

for_each        copy               accumulate
find            copy_backward      stable_sort           inner_product
find_if         swap               partial_sort          partial_sum
adjacent_find   swap_ranges        partial_sort_copy     adjacent_difference
count           transform          nth_element
count_if        replace            lower_bound
mismatch        replace_if         upper_bound
equal           replace_copy       equal_range
search          replace_copy_if    binary_search
                fill               merge
                fill_n             inplace_merge
                generate           includes
                generate_n         set_union
                remove             set_intersection
                remove_if          set_difference
                remove_copy        set_symmetric_difference
                remove_copy_if     push_heap
                unique             pop_heap
                unique_copy        make_heap
                reverse            sort_heap
                reverse_copy       min
                rotate             max
                rotate_copy        max_element
                random_shuffle     min_element
                partition          lexicographical_compare
                stable_partition   next_permutation
                n                  prev_permutation

Table 3: Search-engine interface functions.

int    LexicalScan(char *exp, int *offset, unsigned *hits, int phrase);
void   SearchPhrase(unsigned *hits);
void   NextFoundVerse(int *Book, int *Chapter, int *Verse);
void   EndSearch(void);
int    AddNote(char far *Note);
void   DeleteNote(void);

Listing One


#include <iostream.h>
#include <iomanip.h>
#define __MINMAX_DEFINED
#include <stdlib.h>
#include <algo.h>
#include <vector.h>
int main()
{
    int dim;
    // --- get the number of integers to sort
    cout << "How many integers?\n";
    cin >> dim;
    // --- a vector of integers
    vector<int> vct;
    // --- insert values into the vector
    for (int i = 0; i < dim; i++)
        vct.insert(vct.end(), rand());
    // --- display the random integers
    cout << "\n----- unsorted -----\n";
    for (i = 0; i < dim; i++)
        cout << setw(8) << vct[i];
    // --- sort the array with the STL sort algorithm
    sort(vct.begin(), vct.end());
    // --- display the sorted integers
    cout << "\n----- sorted -----\n";
    for (i = 0; i < dim; i++)
        cout << setw(8) << vct[i];
    return 0;
}


Listing Two


/* bibsrch.c -- dll for bible windows application  */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <ctype.h>
#include "textsrch.h"
#include "table.c"

HFILE fp = HFILE_ERROR;
static HFILE fn = HFILE_ERROR;
unsigned DocNo = FIRSTDOCNO - 1;
char DatabasePath[255];

#ifndef DOS_VERSION
HCURSOR WtCurs;
HCURSOR PCurs;
#endif

static int RetrievalMode;
#ifdef DOS_VERSION
/* ===================================================
   DOS Query Program-specific code
   =================================================== */
static char *BookName[] = {
    "Genesis",
    "Exodus",
    "Leviticus",
    "Numbers",
    "Deuteronomy",
    "Joshua",
    "Judges",
    "Ruth",
    "I. Samuel",
    "II.Samuel",
    "I. Kings",
    "II. Kings",
    "I. Chronicles",
    "II. Chronicles",
    "Ezra",
    "Nehemiah",
    "Esther",
    "Job",
    "Psalms",
    "Proverbs",
    "Ecclesiates",
    "Song of Solomon",
    "Isaiah",
    "Jeremiah",
    "Lamentations",
    "Ezekiel",
    "Daniel",
    "Hosea",
    "Joel",
    "Amos",
    "Obadiah",
    "Jonah",
    "Micah",
    "Nahum",
    "Habakkuk",
    "Zephaniah",
    "Haggai",
    "Zechariah",
    "Malachi",
    "Matthew",
    "Mark",
    "Luke",
    "John",
    "The Acts",
    "The Romans",
    "I. Corinthians",
    "II. Corinthians",
    "Galatians",
    "Ephesians",
    "Philippians",
    "Colossians",
    "I. Thessalonians",
    "II. Thessalonians",
    "I. Timothy",
    "II. Timothy",
    "Titus",
    "Philemon",
    "To the Hebrews",
    "Epistle of James",
    "I. Peter",
    "II. Peter",
    "I.John",
    "II. John",
    "III. John",
    "Jude",
    "Revelation"
};
int FAR _export PASCAL GetVerse(
        char far *Text, char far *Note,
        int Book, int Chapter, int Verse);
void FAR _export PASCAL NextVerse(
        char far *Text, char far *Note,
        int *Book, int *Chapter, int *Verse);
void FAR _export PASCAL PrevVerse(
        char far *Text, char far *Note,
        int *Book, int *Chapter, int *Verse);
static void Display(int bk, int chap, int verse, char *text)
{
    printf("\nDocNo: %u ", DocNo);
    printf("%s %d:%d\n", BookName[bk-1], chap, verse);
    printf(text);
}
static void doquery(void)
{
    static char query[300];
    unsigned hits;
    int c = 0, offset, phrase;
    int bk, chap, verse;
    static char text[512];
    static char note[257];
    while (c != 'x')    {
        c = 0;
        while (c != 'p' && c != 'q' && c != 'n' && c != 'x')    {
            printf("\nP-hrase, Q-uery, N-avigate, or e-X-it: ");
            c = getch();
            putch(c);
        }
        if (c == 'n')    {
            printf("\nN-ext, P-revious, B-ook/chapter/verse: ");
            c = 0;
            while (c != 'p' && c != 'b' && c != 'n')    {
                c = getch();
                putch(c);
            }
            if (c == 'b')    {
                printf("\nBook/chapter/verse: ");
                scanf("%d %d %d", &bk, &chap, &verse);
                *text = '\0';
                GetVerse(text, note, bk, chap, verse);
                Display(bk, chap, verse, text);
            }
            else if (c == 'n')    {
                NextVerse(text, note, &bk, &chap, &verse);
                Display(bk, chap, verse, text);
            }
            else if (c == 'p')    {
                PrevVerse(text, note, &bk, &chap, &verse);
                Display(bk, chap, verse, text);
            }
        }
        else if (c != 'x')    {
            phrase = c == 'p';
            if (phrase)
                printf("\nEnter Phrase:\n");
            else
                printf("\nEnter Query:\n");
            gets(query);
            if (!LexicalScan(query, &offset, &hits, phrase))
                printf("\n\aSyntax error");
            else    {
                int c;
                printf("\n%d hits. Continue? ", hits);
                c = getch();
                putch(c);
                if (tolower(c) == 'y')    {
                    int ct = 0;
                    if (phrase && hits)    {
                        SearchPhrase(&hits);
                        printf("\n%d more hits. Continue? ", hits);
                        c = getch();
                        putch(c);
                        if (tolower(c) != 'y')
                            hits = 0;
                    }
                    while (hits--)    {
                        putchar('\n');
                        NextFoundVerse(&bk, &chap, &verse);
                        *text = '\0';
                        GetVerse(text, note, bk, chap, verse);
                        Display(bk, chap, verse, text);
                        ++ct;
                        if ((ct % 4) == 0)    {
                            printf("\n[more...]");
                            if (getch() == 27)
                                break;
                        }
                    }
                }
                EndSearch();
            }
        }
    }
}
int main()
{
    strcpy(DatabasePath, "bible.dat");
    fp = OpenDataFile();
    if (fp != HFILE_ERROR)    {
        strcpy(DatabasePath, "");
        if ((fn = OpenNotes(DatabasePath)) != HFILE_ERROR)    {
            doquery();
            _lclose(fn);
        }
    }
    _lclose(fp);
    return 0;
}
/* ===================================================
   End of DOS Query Program-specific code
   =================================================== */
#else
/* ===================================================
   Windows DLL-specific code
   =================================================== */
static HINSTANCE hInst = NULL;
int FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDatSeg,
                        WORD cbHeapSize, LPSTR lpCmdLine)
{
    if (hInst == NULL)    {
        hInst = hInstance;
        if (cbHeapSize > 0)
            UnlockData(0);
    }
    return 1;
}
int FAR PASCAL WEP(int nParameter)
{
    if (fp != HFILE_ERROR)
        _lclose(fp);
    if (fn != HFILE_ERROR)
        _lclose(fn);
    fp = fn = HFILE_ERROR;
    return 1;
}
int FAR _export PASCAL RegisterAppl(HANDLE hWnd, char *path)
{
    static HANDLE semaphore = 0;
    HANDLE sem = semaphore;
    if (semaphore == 0)    {
        int plen;
        semaphore = hWnd;
        strncpy(DatabasePath, path, 255);
        plen = strlen(DatabasePath);
        strcat(DatabasePath, "\\bible.dat");
        fp = OpenDataFile();
        if (fp != HFILE_ERROR)    {
            *(DatabasePath+plen) = '\0';
            fn = OpenNotes(DatabasePath);
        }
    }
    return sem;
}
/* ===================================================
   end of Windows DLL-specific code
   =================================================== */
#endif
/* Compute the book/chapter/verse spec from a document number */
void DocNotoBCV(unsigned docno, int *book, int *chapter, int *verse)
{
    unsigned char *tb = BCVtable;
    int chapterct;
#ifdef DOS_VERSION
    DocNo = docno;
#endif
    *book = *verse = 0;
    while (*verse == 0)    {
        (*book)++;
        *chapter = 0;
        chapterct = *tb++;
        while (chapterct--)    {
            (*chapter)++;
            if (docno <= *tb)    {
                *verse = docno;
                break;
            }
            docno -= *tb++;
        }
    }
}
/* Compute a document number from a book/chapter/verse spec */
static int ComputeDocNo(int book, int chapter, int verse)
{
    unsigned char *tb = BCVtable;
    int chapterct;
    unsigned int docno = 0;
    if (book == 0 || chapter == 0 || verse == 0)
        return 0;
    /* ---- get to the book ---- */
    while (--book)    {
        /* ---- bypass chapters in the preceding books ---- */
        chapterct = *tb++;
        while (chapterct--)
            docno += *tb++;
    }
    chapterct = *tb++;
    /* ---- test valid chapter ---- */
    if (chapter > chapterct)
        return 0;
    /* ------ get to the chapter ---- */
    while (--chapter)

        docno += *tb++;
    /* ---- test valid verse ---- */
    if (verse > *tb)
        return 0;
    /* ---- build document number ---- */
    DocNo = docno + verse;
    return 1;
}
/* Read the verse text specified by the current document number */
void ReadVerseText(char far *Text)
{
    /* ---- get offset and bit from DocNo ---- */
    struct versercd vr;
    long offset = sizeof(struct versercd);
    offset *= DocNo-1-(FIRSTDOCNO-1);
    _llseek(fp, offset+BCVOFFSET, SEEK_SET);
    _lread(fp, &vr, sizeof(struct versercd));
    GetLine(Text, vr.offset, vr.bit);
}
/* Read verse text and note text specified by the current document number */
static void ReadVerse(char far *Text, char far *Note)
{
    ReadVerseText(Text);
    GetNote(Note);
}
/* Get the next verse for the user */
void FAR _export PASCAL NextVerse(
        char far *Text, char far *Note,
        int *Book, int *Chapter, int *Verse)
{
    if (RetrievalMode)
        NextNotedDocNo();
    else    {
        if (DocNo == MAXVERSE)
            DocNo = FIRSTDOCNO;
        else
            ++DocNo;
    }
    if (DocNo != FIRSTDOCNO - 1)    {
        ReadVerse(Text, Note);
        DocNotoBCV(DocNo, Book, Chapter, Verse);
    }
}
/* Get the previous verse for the user */
void FAR _export PASCAL PrevVerse(
        char far *Text, char far *Note,
        int *Book, int *Chapter, int *Verse)
{
    if (RetrievalMode)
        PrevNotedDocNo();
    else    {
        if (DocNo > FIRSTDOCNO)
            --DocNo;
        else
            DocNo = MAXVERSE;
    }
    if (DocNo != FIRSTDOCNO - 1)    {
        ReadVerse(Text, Note);
        DocNotoBCV(DocNo, Book, Chapter, Verse);
    }
}
/* Get the verse specified by the user in a book/chapter/verse spec */
int FAR _export PASCAL GetVerse(
        char far *Text, char far *Note,
        int Book, int Chapter, int Verse)
{
    if (ComputeDocNo(Book, Chapter, Verse))    {
        ReadVerse(Text, Note);
        return 1;
    }
    return 0;
}
/* Set the retrieval mode (next note/next verse) for next/previous */
void FAR _export PASCAL SetRetrievalMode(int mode)
{
    RetrievalMode = mode;
}


Copyright © 1995, Dr. Dobb's Journal

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