Introduction

This manual is intended for use by developers who wish to integrate DP4 applications with special devices. The part of the manual relating to Device Definition Hierarchy will also be useful to users who want to add support for new printers that are not supported by the supplied DP4 system database.

  • If you are new to device integration in DP4, read the Overview.

  • If you wish to set up special devices for your system, read the explanation on how devices are defined in Device Definition Hierarchy.

  • If you are an application developer, and you wish to access devices in your program, read the instructions in Accessing devices from DP4 applications.

  • If you wish to write your own OSDI (device interface), read Writing an OSDI. You are recommended to familiarise yourself with the other areas covered in this manual first.

An apology, and explanation is perhaps required for the somewhat confusing terminology used throughout this manual. You can read it here.


What's New in the OSDI Developer's Guide

This page will tell you about recent changes to this manual. The most recent changes are mentioned first

New in 4.623

  • 15 Apr 2005 Added note about printing multiple copies of a document using DFPRINT.

New in 4.621

  • 24 Mar 2004 Added note about changing list of hardware devices available to Windows DP4 programs. See Device Names in the Guide to DP4 Configuration for details.

  • 15 Oct 2003 Information on how to get DFPRINT to create output for other applications added.

  • 4 Oct 2003 Information on using the Keyboard Customisation option of DFSETUP added.

  • 2 Jul 2003 Information on Configuring printers updated to reflect changes in dialogs for 4.621, in particular information about new "Force 4.500" compatibility option and how to selecting a specific Windows printer for DFPRINT to use.

New in 4.620

  • 14 Feb 2003 Information added on Configuring Printers.

New in 4.619

  • This guide was completely rewritten for 4.619.


Overview of OSDI

Purpose

The purpose of the OSDI pack is to allow DP4 developers access from their applications to any device supported by their system in a consistent and portable manner. When the OSDI is used correctly, it should be possible to completely change the actual devices used by the application (even the type of device), or to port the application to another operating system, without needing to change any application level code.

About Devices

Most computers use input and output devices to provide an interface to their external environments. These devices commonly take the form of a keyboard for input and a display monitor for output. Users of DP4 interact with the system with these devices by default.However, applications frequently need to use devices other than or instead of keyboard and monitor for input and output. Furthermore, output frequently means sending information to control a special piece of hardware. There are many kinds of physical device that may be controlled or utilised by a computer, including printers, cash drawers, displays, scales, bar-code readers, magnetic stripe readers and so on.

An application development environment should therefore provide the means to reference as broad a range of devices as possible, in as flexible a manner as possible. An application designer should be able to specify high-level device commands without knowledge of low-level device implementation. An application should have an abstract view of the devices it is handling. That is, it should be possible to replace the underlying definition and implementation of the devices without making changes to the application.

About the OSDI

Itim Technology Solutions' System Device Integration Pack, known as the OSDI pack, provides the application program interface for device handling in DP4. The OSDI is used to create an System Device Driver Interface (also known as an OSDI! See Apology).

The OSDI pack is a source code product. The details vary from platform to platform, but in all cases there are two main parts:

  1. A part that handles communication between the device interface and the DP4 terminal manager. This part does not require any modification

  2. Skeleton code, which you must modify that contains empty versions of functions called by part 1. You modify this part to implement the interface to the a modifiable interface.

With an OSDI you can have:

  • More than one concurrent input device

  • Program control over the input and output devices

  • Synchronous and asynchronous printing, depending on the operating system features

  • Software devices. For example, you could implement a device to count keyboard key presses. This may not be a physical device, but some form of incremented software variable. The key press counter "device" could be interrogated for its value in the same way as a physical device.

  • Error handling

  • Device-independent applications that are portable across different systems. This is achieved because the OSDI defines a device-handling standard. At the application level device handling is independent of operating system.

Any type of device, input or output, can be accessed using the OSDI, provided that it is possible to integrate code that communicates with the device, either directly or using a separate handler. You are not restricted in your choice of device. Typical output devices handled by DP4 applications include printers, customer displays and cash drawers; typical input devices include keyboards, scanners and magnetic stripe readers.

Knowledge Required

The application programmer only needs to understand how to access devices from the application (described here) the protocol at level 1, whereas an OSDI developer must understand the protocol between the DP4 terminal manager and the OSDI. Of course, some DP4 developers will be concerned at both levels. Overall, there are actually three areas which relate to device handling under DP4:

  • Device definition. Both OSDI and application view devices according to definitions held in the system database. These definitions are set up using the DP4 utility DFSETUP

  • Application-level device access. The DP4 application interface allows the devices defined using DFSETUP to be selected and utilised

  • Direct device manipulation. Your OSDI will have to translate requests arriving from the application into commands that the actual devices, or their drivers understand.


Device Definition

Device definitions are held in the system database. You define them using the DP4 utility DFSETUP. Devices are always described in terms of a general Device Group. A Device Group maps to a real-world physical device. Such devices are defined in terms of lower-level input and output components. It is often the case that physical devices do consist of input and output components - a till is a typical example. The Device Group is therefore defined as owning one or more output devices (for example printers, customer displays, cash drawers) and one or more input devices (keyboard, scanner, magnetic stripe readers).

A more detailed description of all this can be found in Device Definition Hierarchy.


Output Devices

Output device definitions may specify more than the output destination - they may also contain initialisation information. This means that when your application selects a device for output, it not only notifies the OSDI which component of the Device Group is to be used, but it also passes some information to define the way output is to be handled.

For example, you may have a printer on a till capable of printing in various character sizes. DP4 allows you to define devices in such a way that the definition encapsulates not only the destination of output (the till printer) but also output data characteristics (in this case character size). One output device may thus be diversified (you may define three printers, all printing at the till printer but all at different character sizes).

DP4 uses the concept of CODs (Component Output Devices) to define generic output device types. A COD definition may include a set of parameters where each individual parameter can only take one member of a defined range of values. The selection of one set of specific parameter values from the defined range of all possible values produces a POD (parameterised output device). In this way a single COD definition may give rise to one or more PODs. DP4 applications always view devices as PODs.

More information on CODS and PODS can be found in CODs and PODs.


Input Devices

Input devices are also stored on the system database, using DFSETUP. Input device definition allows you to specify multiple input devices owned by a given device type. This means that more than one input device may be active at any one time in an application.

A typical input situation is one where a till has both a keyboard and a scanner attached. The cashier is free to use either device for input of a product code, or both.

You can find more information on input devices in Input Devices of a Device Group.

Commonly, in a system using an OSDI, the user will be running using a non-standard keyboard. As a result you are likely to want to modify the standard userdata.sys keyboard settings, either to generate a special userdata.sys which a developer running on a normal PC without the special keyboard can use to test the application, or to create a suerdata.sys to be used on the live system. You can find more information in Keyboard Customisation.


Application Programming

DP4 application programs that use devices may be written in QA Build, or C.

The OSDI helps the application developer in three ways:

  • It allows him or her to write standard DP4 applications that can be used with any combination of input and output devices

  • The exact implementation of the special devices is hidden from the application programmer

  • It localises and minimises any device-specific code that must be written

A DP4 application can make calls to handle both output and input devices. The DP4 device interface hides the device implementation from the application via a set of general device handling calls. These calls can only reference devices that have been defined on the system database. For output devices the application program generally only has to select the required output device from the system database and then output the desired data to it. Output to any device uses the same programming techniques as are used for general purpose printing in DP4 (as explained here). The application may contain some error handling, though this will be localised as far as possible. The application may also request status information from the device.

Input device handling generally consists of selecting a range of input devices from which input is acceptable. The application than calls the normal APIs for data input. Once entry is complete the application may want to know where the input came from. This information is available via a simple API.


Output Devices in DP4 Applications

The application regards all output as printing, but the output device need not be a printer; for example customer displays and cash drawers may be output devices. Some output devices may be used purely for control purposes. An example of this is an output device which sets parameters to control the subsequent reading of a magnetic stripe by an input device. Such a control device need not correspond to any physical equipment; it might just set up information accessed by the OSDI stripe reader software.

Printer output can only be sent to one POD at a time (in addition to screen output and file output). The POD is preselected by the application program. However, the POD might get translated by the OSDI into multiple physical devices.

Output to OSDI compared with output to regular print device

Consider the situation where a program must send the text "Ice cream $2.50" to be printed:

  1. On a regular printer at 12 characters to the inch

    and

  2. On a customer display attached to a till

In the first case you do not need to use the OSDI. DP4 can route the output to the required printer port and send the required escape sequences by using a POD based on a device group for a regular printer (defined in DFSETUP). The second case normally requires an OSDI to access the customer display, (because the manufacturer's interface to the display probably uses a special library and a program won't be able to access all (or even any) the functionality of the display without using this. The important point is that the application uses the same calls to generate output in both cases, but does not need to know how the data is managed once output has been sent.

In both case A and B, the application programmer calls generic functions to specify the output device and to output data. In both cases the same two calls are made to produce output, differing only in parameter values:

  1. Open the printer, specifying the destination device (regular printer or customer display). The destination device will include device characteristics such as character size

  2. Print the text (same in both cases)

For case A, where there is no OSDI, the printer is addressed through the operating system. The character size is implicitly sent, providing it has been specified as part of the device definition (for example as a suitable escape sequence, or as part of the map definition of a 4.6xx program that prints to the DFPRINT device).

For case B, the display is not attached to a regular printer port, but to a special interface (an OSDI). It is the OSDI that interprets the device definition and characteristics and performs the device-dependent output.

In order to select a device, and any associated print characteristics, your application program selects a POD. In both case A and B there must be a POD definition based on a COD definition. After the POD has been selected DP4 print instructions are used to send output.

If you want to use some other kind of printer at a later date, you do not need to modify your program. You can just allocate a different COD to the POD addressed by your program.


Writing an OSDI Overview

Writing an OSDI consists of modifying the device interface module of the OSDI. This module provides empty functions each with a specific purpose (initialise the devices, start input, send output, and so on). These functions broadly map to the application-level functionality available to you in DP4. You supply the appropriate code in each function for your specific Device Group.

For example, suppose the way to write to the display in case B above is to move the data to a video RAM held at D000:0000. You must write a piece of code to place the data at the given address. The OSDI is automatically passed the two pieces of data that you require: the identifier of the POD that ultimately receives the output and the output data itself. You modify a specified function to handle the output - this function is called when the application "prints" to the display.

The device interface module must be compiled and linked with the supplied object modules that comprise the rest of the OSDI. This produces a device-specific OSDI executable program. More information on how to write an OSDI using the OSDI pack can be found here.

The general structure of the OSDI is fairly consistent across the various operating systems. For example the skeleton functions to be modified have the same names and parameters in MS-DOS,OS/2 and Unix/Linux. However, the Windows OSDI uses a different set of functions, and you should therefore be careful only to study the appropriate pages in the part of the manual that deals with writing OSDIs.


Introduction to the Example Device

An example device will be presented throughout this document. This will help you to relate to and understand the three key areas of DP4 device handling: device definition, application device handling and OSDI modification. The device we will use will be an imaginary point-of-sale terminal, which we will call "the DP4 Till". The DP4 Till can be broken down into certain areas of functionality which can be mapped to CODs and PODs:

  • A slip printer, which can be used to print customer receipts
  • A journal printer
  • A cash drawer, which is always either open or closed, and can be interrogated for its current state
  • An identification number, held in some internal firmware and returned in response to the appropriate request
  • A scanner for reading bar codes
  • A keyboard for manual input

The sections that follow will use this device to demonstrate some of the principles and methods that you may encounter when accessing a device from a DP4 application.


Device Definition Hierarchy

Devices are set up on your DP4 system database using DFSETUP. Devices are defined in a hierarchical fashion in DFSETUP, with the Device Group at the highest level. The Device Group is then further defined in terms of component input and output devices. The process of creating a device group with DFSETUP is described in Configuring a Device Group. Applications do not use CODS (component output devices) directly but PODs (parameterised output devices). However, the interface between the DP4 Terminal manager and the OSDI uses the COD information directly. Applications do use input devices directly, as nothing would be gained by an abstraction equivalent to the one between CODs and PODs.

Typically, although not always, an OSDI handles just one Device Group (such as a printer or Point of Sale (PoS) terminal), and applications that use the OSDI will only access PODs belonging to that device group, (and possibly device groups that do not require an OSDI).

A Device Group (such as our DP4 Till) may contain several output and input devices. The example DP4 Till has two input devices (keyboard and scanner) and several output devices (printers, drawer, and so on).

The following diagram shows a Device Group, and the relationship between Device Group, CODs, PODs and input devices:


CODs

Output devices owned by a Device Group are referred to as CODs. Device Groups own CODs in a one-to-many relationship. The relationship between a Device Group and its CODs frequently matches the relationship between the physical device and its components.

In the case of our DP4 Till, it is immediately apparent that the Till itself (as the Device Group) owns a slip printer, a receipt printer and a drawer. However, other functionality is available, such as the till identification value held in firmware. A Device Group should be broken down into CODs according to the functionality you wish to access from your OSDI.

It is often useful to list the available functions for a Device Group and use this list as a guide to COD definition (described in more detail here). In terms of its functionality the DP4 Till breaks down into the following CODs:

  • An initialisation device
  • Printers
  • A drawer
  • A till identification

The initialisation device is a special COD, used to initialise the Device Group as a whole (for example to initialise the whole DP4 Till). This COD must be defined (DFSETUP provides the initialisation device definition automatically when you set up a Device Group). The initialisation device is used when an application starts using a new Device Group. Once the initialisation device has been selected, all the output and input devices belonging to the Device Group become available to the application.

The application does not view the Device Group directly in terms of CODs. Instead, PODs provide a further level of abstraction.

For the DP4 Till we will assume that the two printers (slip and journal) are functionally identical. Their definition can therefore be encompassed in a single printer COD. The printers can be distinguished at the POD level using COD parameters. (This is done for the sake of example, it would not necessarily be appropriate in real life.)

COD Parameters

As just mentioned, COD Parameters provide a mechanism for the application to send additional information to the OSDI. There are several different type of parameter possible, which are described in more detail in the section on COD Parameter Definition. The original motivation for COD Parameters was to extend the facility provided by printer initialisation strings in DP4. For example, a properly set up COD can make it easier for a relatively non-technical user to set up a POD which prints to a printer in Landscape format using Times Roman font, rather than Portrait format and Courier font. Of course setting up COD parameters to do this can be quite a time consuming task (and if you are using DP4 v4.616 or later on Windows you can achieve far greater control over any printer by simply printing to the DFPRINT device). Devices such as passbook printers often have extensive lists of parameters.

An alternative to using parameters is for the OSDI to define a format for the data the application must print, and to interpret the data directly. For example for a cash drawer you could do either of the following:

  • Define a parameter that takes the value 0 or 1 (or N or Y etc). 0 means close drawer, 1 means open drawer
  • Don't use a parameter, but interpret the data printed by the application as a command to open or close the drawer

Parameters are appropriate where there are relatively few possible "control values" that the application needs to pass to the device, and where it is unlikely that an application would need to pass a calculated value for such control information. For example an application would most likely not need to know the exact dimensions of a passbook. Using parameters in this case means that the application would not need to be changed if a new passbook is adopted - only the parameter values for the POD need to be changed.

Conversely passing data directly from the application rather than using parameters is appropriate when the application does need control over the device. An obvious example here might be bar code printing based on article numbers. Although the data sent from the application is probably not printed directly, (the OSDI would almost certainly be the correct place for the extra logic that would be needed for printing the bar code in the correct dimensions and with the correct separators), using parameters would be extremely unwieldy in this case (though conceivably you might use parameters to select a bar code type).


PODs

Each POD corresponds to a particular choice of COD. The relationship between CODs and PODs may be one-to-one or one-to-many. A one-to-many mapping between a COD and several PODs is achieved using COD parameters.

As part of its definition, a COD may have parameters. Each parameter has a defined range of values. When defining a POD, one value from the range for each parameter is chosen. The exact choice of parameter values allows you to diversify a single COD into multiple PODs.

For example, the DP4 Till slip and journal printers are functionally identical. Furthermore, let us assume that there is a requirement to print 10-point text on the journal but 18-point on the slip, and that these size requirements may need to be altered in the future. (Remember that the example DP4 Till is an imaginary device and the requirements and solutions have only been chosen to illustrate certain key areas in DP4 device handling.)

It might be sensible to define a generic printer COD for the Till. You can distinguish it at the POD level by defining two parameters. The first consists of a character defining the printer destination, 'S' or 'J' (for slip and journal respectively). The second takes a numeric value to define the character size, 10 or 18. Therefore there are two parameters each having two values available for use:

  • Parameter 1 (destination printer) : 'S' or 'J'

  • Parameter 2 (character size) : 0x0a (10-point) or 0x12 (18-point)

When you set up a POD derived from the printer COD, its definition will include the two parameters, and these can only take a combination of the available values above. The printer PODs are defined as:

  • Slip printer : parameter 1 = 'S' parameter 2 = 0x12

  • Journal printer : parameter 1 = 'J' parameter 2 = 0x0a

The parameter string will be passed ultimately to the OSDI. The example OSDI will read the first character to decide on the destination of any printer text, and the second to set the character size.

There are a number of benefits to the COD-POD relationship. A major benefit is that a POD may be moved from one owner COD to another without re-compiling the application. This is useful when your application views the two output devices as functionally identical but when their underlying implementation differs. The OSDI may have to be enhanced and a new COD defined, but the application can continue to use the original POD. The POD layer provides a high degree of output device abstraction for the application programmer.

This abstraction also allows individual COD/POD definitions to be altered without affecting the application. For example, if the character size requirement for an existing printer changes, you only need to alter a parameter value. No re-compilation of your application or OSDI is necessary. Furthermore, the fact that PODs are grouped according to their owning COD promotes a modular structure for your OSDI, allowing it to carry out common processing for related PODs. PODs are distinguished in an OSDI by their parameter values.


Input Devices of a Device Group

A Device Group may own up to 16 input devices. An application program may request input from any combination of the available input devices belonging to the active Device Group.

DFSETUP restricts the values that input devices may take to powers of two. You are prompted for input device names and these names will be available to you in a QA Build application that has selected the owner Device Group. A DP4 C program must reference the numeric value (preferably using a #define name - however you will have to create these for yourself as DFSETUP does not have a facility to generate them for you).

Powers of two are offered as input device values to enable you to combine input sources in a bit mask. The advantage of using these values is that your application may combine them to specify multiple input sources. The combined value is passed from your application to your OSDI. For example, suppose the keyboard is defined as the value 1, and the scanner as value 2. You can select just an individual input device in your application - choose 2 to select the scanner only. If you want to select both the scanner and the keyboard you would pass the value 3 (2+1).

In the OSDI you can isolate which input devices have been selected using the bitwise AND (&) operator on the mask. Further detail on selecting input devices from applications is given in Enabling Input Devices (C Programs) and Enabling Input Devices (QAB).

The DP4 Till will use either a keyboard or a scanner, an application using the Till may request input from both simultaneously.


Configuring a Device Group for an OSDI

At the application level DP4 uses the same device architecture and APIs for communicating with both "ordinary" printers and specialised multi-component devices such as Retail or Financial POS equipment controlled via an OSDI. As a result setting up a printer may seem a rather complicated business. Conversely, when setting up a Device Group for an OSDI some of the printer facilities may be irrelevant.

The following is a guide to defining a Device Group in DFSETUP. On this page we will use a device group for the "DP4 Till" (a device using an OSDI) as an example. For an example of configuring a Device Group for a printer OSDI click here.

  1. From the System Setup (DFSETUP) main menu, select Printer and device set up..., then select Device group (DG) description. This choice will allow you to define a Device Group

  2. Enter the name you wish to give to your Device Group (in this case DDI_TILL). You will be prompted to add your new Device Group to the system. After adding it the Device Group Editing Menu is opened.

  3. (Optional) Select Copy and select one of the existing devices if you think that your device is similar to one of the devices already on the system database. This will copy all the information for the selected device group. (A bug in DFSETUP means that the screen display is not correctly updated to show the copied properties, in releases of DP4 prior to 4.620.)

  4. Select Device group description from the Device Group editing menu. This will place you in the screen below (the screen shown is for OS/2 or MS-DOS):

  5. Enter the description of your Device Group. The next part of the dialog is slightly different depending on whether you are using release 4.621 or an earlier release of DP4:

    • If you are using release 4.620 or earlier then you can accept all the default values until you are prompted for Special device. Set Special device to Yes (or check the checkbox). This tells DP4 that your Device Group uses an OSDI.

    • If you are using release 4.621 or later then accept all the default values until you are prompted for the Device Family. Select OSDI controlled device. This tells DP4 that your Device Group uses an OSDI. You can accept the default values for Soft form feed and Null character

  6. Set Hidden device to Yes to prevent the DP4 Till appearing as an available option for general purpose printing.

  7. Select printer port NUL, or, if you are running on Unix and Linux, prn7. The combination of special device and this port (the actual port is the same in both cases) signifies to the DP4 Terminal Manager that an OSDI is required. For some operating systems the output is actually redirected to NUL (for example MS-DOS), for others NUL is not used. For example under OS/2 the OSDI is a Dynamic Link Library and the NUL port is not used.

    Note that the other printer ports listed are for regular printers. Even if your OSDI uses one of these ports for communication with the device you must specify the NUL port or prn7.


COD Definition

To define the CODs of your Device Group:

  1. After you have successfully defined your Device Group, the Device Group Editing menu will reappear. The items Boxes and control strings and additional attributes are used to set up the translations for DP4 box characters and to select escape sequences used by colored maps in 4.5xx. These items are normally only needed for supporting regular printers, and are not generally used in the OSDI. Instead select the item Component output devices (CODs)...

  2. Define the CODs owned by your Device Group. Each COD is identified by a character. You will see that the initialisation device is already defined for you as COD '1'. You must have this COD. Define the remaining CODs required for the DP4 Till by selecting New COD record for each COD required.

    Define the following CODs for the DP4 Till:

    Each time you choose to add a new COD record you will be prompted for certain COD characteristics. Here is the printer COD definition as an example:

  3. For COD code enter a character to identify your COD. It is often useful to specify this COD identifier as a mnemonic (for example 'P' for printer). The COD identifier will be used when defining PODs and will be referenced by your OSDI. Choose the codes shown in the above CODs screen for the DP4 Till.

  4. Answer Yes to the question Requires device driver? (or check the corresponding checkbox on Windows systems). This guarantees that the COD identifier will be sent to your OSDI whenever your application accesses a POD. Normally your OSDI can work out which POD is being referenced because the DP4 Terminal Manager delivers two things; the COD identifier and its associated parameter string. The DP4 Terminal Manager selects these according to the POD specified by the application.

    For example, suppose that the application selects the DP4 Till slip printer. The application can reference this by logical device name - for example SLIP_PRINTER (POD set-up is explained later). The DP4 Terminal Manager will pass the corresponding COD identifier ('P') and parameter string ('S' 0x12).

    The COD identifier does not always have to be sent - you can answer No to Requires device driver ? in some circumstances. For example, the mandatory initialisation device '1' is automatically referenced in any OSDI when a Device Group is selected by an application. The DP4 Terminal Manager does not therefore need to send the COD identifier '1' to the OSDI. However, the parameters for a COD are always sent to the OSDI.

  5. You should only answer Yes to the question Send CR after initialisation? if a COD has parameters, and you want the parameter string to be terminated with a carriage return.

    In our example, only the printer COD expects parameters. So only answer Yes to this question in the case of the printer COD.

  6. Enter a text description of your COD.

    After answering the COD definition questions you will be prompted with the COD editing menu:

Having set up the COD definition you are now ready so set up COD parameters for CODs requiring them. This is described on the next page.


COD Parameter Definition

  1. Select Parameters from the COD editing menu. In our example, only the printer COD takes parameters:

    In our example two parameters must be defined, the first specifying the printer type (slip or journal) and the second specifying the character size (10 or 18 points):

    Parameter types may be character strings, escape sequences, hex integers in character form (for example, 0xff becomes "0xff") or decimal integers in character form. In the above example the point size is defined as an escape sequence. In our example the DP4 Till printers take numeric values for character size, and so an escape sequence (rather than a hex integer as string) is used.

    Note that all the parameter values chosen to define a POD will be concatenated into a single string when an application selects the POD. Column and length refer to position and size of the individual parameter value within the whole parameter string. The positions are counted from 1 (not 0) as the beginning of the string. If you enter NULL for the position, the parameter will be adjacent to the previous one in the string with no intervening blanks; thus you must put in non-NULL positions if you want the parameter values to be blank-separated. Parameters defined as escape sequences do not take a length because the length is implied by their possible values.

    Hex and decimal integers in character form can have a range and default value specified at this stage. This is where their values are defined and so the separate 'values' option on the parameter menu is unavailable for these parameter types.

  2. Set the possible values for your parameters. Select Values from the Parameters menu. Select the New device parameter value record on the Parameter values list, and add the parameters you require. The printer destination parameter is defined as follows:

    The character size parameter values are defined as:

  3. Finally, select an appropriate default value in both cases. This is the value a printer POD will take by default.


POD Definition

When you have defined your Device Group and its CODs, you are ready to set up PODs.

  1. Return to the Printer Characteristics menu and choose Parameterised output device (POD) configuration...

    Note that you must set up a POD for the initialisation device. This will be the initialisation POD, and without it you will be unable to select your Device Group within an application. In our example we will set up a POD TILL_INIT that maps to COD '1' (the initialisation device) of Device Group DDI_TILL. For each POD:

  2. Supply an appropriate name - this will be used by your application to reference the POD

  3. Choose the owning Device Group (DDI_TILL for every POD in our example)

  4. Choose the relevant COD that this POD will be mapped to (Ctrl+P supplies a list of available CODs)

  5. Supply a text description for the POD

    The slip printer is a typical POD belonging to our Till:

    Both the slip and journal printer PODs are based on the printer COD. Configure them in terms of parameter values. To do this choose Configure device and select parameter values as shown in the table below:

    POD COD Parameters Values

    TILL_INIT

    '1'

             None

    SLIP_PRINTER

    'P'

             Printer type
    Character size     		'S'
    0x12

    JOURNAL_PRINTER

             'P' Printer type
    Character size     		'J'
    0x0a

    TILL_ID

    'T'

             None

    TILL_DRAWER

    'D'

             None


Input Device Definition

A device type can potentially take input from a variety of input sources simultaneously. For example, our DP4 Till provides a choice of input device - the keyboard or scanner.

To set up input devices (using the DP4 Till as an example) :

  1. From the system setup menu (DFSETUP) choose Printer and device set up... followed by Device Group description...

  2. Select your Device Group (for our example, DDI_TILL) and proceed to Set up device group...

  3. Select the option Input devices. For the DP4 Till supply input device information for input devices 1 and 2 as follows :

Note that if you only intend to use a keyboard as an input device, you do not need to specify any input sources in DFSETUP. Input device 0 is used for keyboard input by default.


Configuring a Printer Device Group

At the application level DP4 uses the same device architecture and APIs for communicating with both "ordinary" printers and specialised multi-component devices such as Retail or Financial POS equipment controlled via an OSDI. As a result setting up a printer may seem a rather complicated business. Conversely, when setting up a Device Group for an OSDI some of the printer facilities may be irrelevant.

The following is a guide to defining a Device Group in DFSETUP. On this page we will use an HP Laserjet printer as an example. For an example of configuring a Device Group for an OSDI click here.

Is it really necessary?

Before going to the trouble of setting up a new device group you should ask yourself if it is really necessary to do so. In particular, if you are using a 4.6xx release of DP4 and want to print to a regular printer, by far the best way of printing will usually be to print to the DFPRINT print device. This can print to any printer that comes equipped with a Windows Printer Driver. It may also allow you to print to other types of device: for example Windows 2000 comes with a Fax Printer driver, and commercial drivers are available that can do things like creating PDF documents.

Up to release 4.620 printing to DFPRINT always selects Windows' current default printer as the device to print to, although the user can select any other installed printer using a printer set up dialog that is displayed at run time. From release 4.621 you can configure multiple DFPRINT devices each mapped to a different Windows printer. For details see Selecting a Specific Windows Printer for DFPRINT.

From 4.621 it is also possible to configure DFPRINT devices that will convert the output generated DP4 into files that can be read by other applications. For details see Creating Output for Other Applications.

DFPRINT is not available in 4.5xx releases of DP4, but the similar though less powerful utility, WINPRIN, can be used in a similar way.

To Configure a Printer:

There are three main dialogs to be completed when setting up a device group for a printer:

  • In Device Group Description you set up a name for the device, as well as descriptive text that will be displayed when you are prompted to select a device group elsewhere. This dialog also sets important properties such as the destination port to be use for output to the printer. The dialog varies slightly depending on the release of DP4 you are using. Please select the link appropriate to the release of DP4 you are using.

    • Device Group Description for release 4.620 or earlier
    • Device Group Description for release 4.621 and beyond

  • The Boxes and Control Strings dialog allows you to define the characters used to represent DP4 box characters (used by the Table Plan utility for example) and strings for initialising and deinitialising the printer, as described below.

  • The Additional Attributes dialog allows you to define escape sequences that will be used to when printing 4.5xx coloured text and maps. The use of this dialog is explained below in Changing the Appearance of Printed Text.

In order to print to your printer using the Device Group information you have set up you need to follow the steps described in POD definition to create the device information that DP4 applications use. Typically there will be only one POD for printing to an ordinary printer.

Initialising the Printer

You may wish to send an intialisation string to your printer. For example you may wish to specify a font size or character set. You can specify a short (7 characters max) initialisation string from the Boxes and Control Strings option in the Device Group Editing menu. There is also a similar termination string used when releasing the printer. If you want to specify a longer initialisation string then you have to use COD Parameters. To access the COD parameters for a printer select Component Ouput Devices (CODS) from the Device Group menu, accept the default COD (1), and then choose COD Parameters from the menu that appears. This will take you to a Window of the Device Parameter records. From there follow the instructions for setting up COD parameters as for setting up an OSDI. You will need to have your printer documentation available for details of the escape sequences used by your printer.

In DP4 printer control escape sequences are always terminated by a NUL (binary 0) character, and it is impossible to send such a character to a printer from DP4 directly. Unfortunately a few printers do make use of such characters for selecting some options. The only way to access such options from a DP4 program is via an OSDI.


Device Group Description for 4.620 or earlier releases

  1. From the System Setup (DFSETUP) main menu, select Printer and device set up..., then select Device group (DG) description. This choice will allow you to define a Device Group

  2. Enter the name you wish to give to your Device Group (in this case HP_LASER). You will be prompted to add your new Device Group to the system. After adding it the Device Group Editing Menu is opened.

  3. (Optional) Select Copy and select one of the existing devices if you think that your device is similar to one of the devices already on the system database. This will copy all the information for the selected device group. (A bug in DFSETUP means that the screen display is not correctly updated to show the copied properties, in releases of DP4 prior to 4.620.)

  4. Select Device Group description from the Device Group editing menu. This will place you in the screen below (the screen shown is for OS/2 or MS-DOS):

  5. Enter the description of your Device Group. Consult the documentation that came with your printer and the online help messages to work out the correct answers for the other questions on this screen (though the defaults usually work well), until you are prompted for Special device, . Set Special device to No. This tells DP4 that your Device Group does not use an OSDI. Set Hidden device to No if you want the user to be able to select this printer as a general purpose printer in DP4 utilities, and Yes if the device is only accessible to programs that select it explicitly.

  6. Select the appropriate printer port for this print device. PRN is usually a good choice, but you may want to use one of the other ports, especially if more than one printer is connected.

  7. In order to access your printer from programs you need to create one or more PODs for it. Follow the instructions in POD Definition. To access the PODs follow the instructions in Accessing PODs from DP4 Applications


Device Group Description for 4.621 or later releases

  1. From the System Setup (DFSETUP) main menu, select Printer and device set up..., then select Device group (DG) description. This choice will allow you to define a Device Group

  2. Enter the name you wish to give to your Device Group (in this case HP_LASER_LAND). You will be prompted to add your new Device Group to the system. After adding it the Device Group Editing Menu is opened.

  3. (Optional) Select Copy and select one of the existing devices if you think that your device is similar to one of the devices already on the system database. This will copy all the information for the selected device group.

  4. Select Device group description from the Device Group editing menu. This will place you in the screen below:

  5. Enter the description of your Device Group. Consult the documentation that came with your printer and the online help messages to work out the correct answers for the other questions on this screen (though the defaults usually work well), until you are prompted for Device Family. Select Character Mode Device. This tells DP4 that your Device Group does not use an OSDI but sends characters to one of the device ports on the computer (such as LPT1 or COM2).

  6. Leave the Hidden device checkbox clear if you want the user to be able to select this printer as a general purpose printer in DP4 utilities, and set it if the device should only be accessible to programs that select it explicitly.

  7. Select the appropriate printer port for this print device. PRN is usually a good choice, but you may want to use one of the other ports, especially if more than one printer is connected. Although the list of available device names presented by DFSETUP is hard-coded, from Release 4.621(8) you can change the devices available at run-time by adding redirections in the DP4 Configuration File. See Device Names in the Guide to DP4 Configuration for details.

  8. In order to access your printer from programs you need to create one or more PODs for it. Follow the instructions in POD Definition. To access the PODs follow the instructions in Accessing PODs from DP4 Applications

  9. If you would like detailed control over how text appears on the printed page using coloured text to select print attributes then check the "Force 4.500" option. Please note that if you use this option you must set up all the maps you need for printing to this device using the 4.5xx version of MAPEDIT. Attempting to print to a printer with this option checked using maps that only exist in 4.6xx format will cause system error 131 (Map not found). Refer to Changing the Appearance of Printed Text for information on using coloured text.


Boxes and Control Strings

The Boxes and Control Strings Dialog sets up two distinct types of information:

  • It sets up the basic control strings used by the printer: initialisation and termination sequences for when the printer is first accessed and finally released, and line termination (Carriage Return) and page termination (form feed) sequences. For most printers the default sequences work well.

  • The second part of the dialog sets up the characters that will be used to translate DP4 box characters. You have four choices:

    • You can remove all box characters from maps by translating them all to spaces (20h)
    • You can represent boxes using a selection of standard ASCII characters + (2bh), - (2dh) and | (7ch)
    • You can use the IBM MS-DOS box characters, if your printer understands them. Most printers that can be printed to from MS-DOS programs will do. These are shown below:
                        X       X                       X       X
 0  20h   0X c4h  0  b3h  0X c0h X0  c4h X0X c4h X0  d9h X0X c1h

                  X       X                       X       X
 0  b3h   0X dah  0  b3h  0X c3h X0  bfh X0X c2h X0  b4h X0X c5h
 X        X       X       X       X       X       X       X
    • Your printer may have some other range of characters reserved as box characters. Consult your printer documentation.

After changing box characters you may like to use the Test control strings option. Exit from the Device Group Editing menu to return to the menu containing this option.


Changing the Appearance of Printed Text

If you want to use the full range of capabilities of modern printers from a DP4 program, then you should probably not be reading this page, but using the DFPRINT device instead!

Most printers will have capabilities such as printing text underlined or bold, and you will probably want to access such capabilities from your programs. There are various ways to achieve such effects:

  • You can define a second COD for the printer allowing you to create PODs that are "subdevices" of the printer. These CODs can have parameters that with appropriate escape sequences for the effect you want. This type of approach would be suitable for an effect that was applied globally to several lines or even complete pages of output. Note that although your program does not have to know much about the specific details of the printer, it does need to know that it has a POD capable of acting as a subdevice, and needs to select it, so changes may well be required to the program to get it to print to another printer - at the very least if you want the program to work with a different printer you would need to create a new device group and then change the PODs used by the program to access the new device group.

  • You can embed control sequences directly into the map. This approach will work with any release of DP4, however you will not be able to do this except by editing MAPPOST format output with a text editor capable of editing arbitrary hex characters to a file. An alternative is to include fields in the map into which your program prints escape sequences. This approach can be used from both C and QAB programs. However it has two drawbacks:

    • The alignment of text may well be upset because space that DP4 thinks has been used for regular output gets swallowed by escape sequences.

    • Your program is printer specific.

  • The third approach is only available with release 4.5xx maps but can work very well. Maps for printing are specially coloured. In the printed text the colours are not regarded as real colours, but as requests for various special print formats. For example, by convention, blue ink represents underlined text, bright ink represents bold text, blue paper, represents double height text, and green paper represents double width text (so dark grey ink on cyan paper requests bold double height double width text). Use the Additional attributes dialog from the Device Group Editing menu to set up suitable escape sequences to select these attributes in the printed text. If desired you could use other colours to select italic text or some similar attribute.

    This is the best approach for 4.5xx releases. If you are using a 4.6xx release then you need to force your program to use 4.5xx maps when printing to take advantage of this facility. Depending on the 4.6xx release you are using there are different ways of doing this:

    • Printing using 4.5xx maps in 4.6xx releases up to 4.620

      You can force a DP4 program to run as a 4.5xx program even if you are using 4.6xx by using the use600=0 DP4 Configuration setting, either in an INI file, or (in 4.620) via the command line for the program.

      In this case your program must make do with the less sophisticated 4.5xx user interface in this release, or you must split the program so that the printing is in a separate executable. (You could even use this approach with QAB programs by copying progrun.exe to progr500.exe and adding a use600=0 setting in a [progr500] section of your DP4 configuration file.)

    • Printing using 4.5xx maps in 4.621 or later releases

      You can still use the technique that worked up to release 4.620, but from release 4.621 setting the "Force 4.500" property in Device Group Description) allows a program with a 4.6xx style user interface to print using 4.5xx maps. This means your program does not have to be slpit into separate executables, or use the old fashioned 4.5xx style user interface for interaction with the user.


Configuring DFPRINT PODS

Prior to release 4.621 there was no way to specify a specific printer to use when printing to the DFPRINT print spooler, or to create a file that could be viewed in other applications. From 4.621 you can set up multiple instances of the DFPRINT device each mapped to a different Windows printer, either using a real device name or a logical name that is mapped to a real device using an environment variable or entry in the DP4 configuration file, and prefix the name with options that will cause output to be converted to formats usable by other applications, and/or (from 4.623) print multiple copies of a document.

Selecting a Specific Windows Printer for DFPRINT

To use this facility create a new POD based on DFPRINT and then select the Configure POD option. Instead of the normal configuration dialog, a simple dialog is displayed that allows you to enter the name of the Windows printer to be used is displayed. You can do any one of the following:

  • Leave the name blank to use the default printer.

  • Enter the name shown for the printer in Explorer to use another local printer.

  • Enter a printer name in the form \\server_name\printer to use a network printer.

  • Enter a name of the form %MY_PRINTER to get the printer name from the environment variable MY_PRINTER (which you must ensure is set up).

  • Enter a name of the form $printer to get the printer name from the value of a DP4 configuration file (DP4.INI) variable called "printer". This variable can be set separately for each program section.

Creating Output for Other Applications

It is also possible to prefix the printer name (if any) set up in the Configure POD dialog with one of four special strings that cause some modifications in the way DFPRINT behaves.

Each of the special strings may cause DFPRINT to convert the printed output to a file:

  • !0 creates one large web page with a .htm extension containing all the pages. Prior to 4.621 a .css file containing the stylesheet for the report is also generated, but from 4.621 the stylesheet is included in the .htm file.

  • !1 creates multiple .htm files each containing one page of the output. From 4.621 each .htm file contains all necessary style information.

  • !2 creates a .rtf file containing all the pages of the output encoded as a metafile. The file is suitable for display using Microsoft Word, or Microsoft Word Viewer, which is a free utility available from the Microsoft Web site. These .rtf files are not displayed correctly by some other applications, such as Wordpad, that rely on Rich Edit controls.

  • !3 also creates a .rtf file, but one which is compatible with applications that rely on Rich Edit controls. It should be noted that this type of file may be a lot larger than files created using !2, and also that not all aspects of the display are correct (though this is only likely to be a problem with controls that are not usually included in print-outs, such as buttons,icons and VCRs).

Whether or not the converted output file is actually created depends on how DFPRINT is invoked: if DFPRINT is invoked interactively, then the conversion only takes place if the user uses the Save option; if DFPRINT is invoked in batch mode, then the file will automatically be created unless the calling program specifies that output is only to be printed.

The 4.621 system database contains two new POD devices, Word_RTF_Meta and WordPad_RTF_Meta that create !2 and !3 files. Both these filetypes may be very useful for sending a report via e-mail:

  • The files created cannot easily be edited
  • The files created are stand-alone: they can be viewed on machines without DP4, and any images contained in the report are embedded in the file.

It should be noted that the .RTF file-format is not space efficient, so you may choose to zip the files before sending them.

Printing Multiple Copies

From 4.623 you can configure a DFPRINT device that will print multiple copies of a document. This is done in a similar way to the way in which the save format is set - you prefix the printer name (if any) with a special string: *nn where nn is a number denoting the number of copies of the document to be printed. For example to print two copies of each document specifiy *2 before the printer name.

Printing multiple copies of a document relies on the printer driver to support this feature. DFPRINT itself does not have logic for printing multiple copies of a document. Therefore, if you require this facility, you should check that the printer the system will be deployed with supports printing of multiple copies.

If DFPRINT is invoked in a way that allows the user to select whether or not to print, then when the user presses the Print button the print dialog that appears will have the number of copies to be printed set to the appropriate value, but the user will be able to over-ride this. However, if the print dialog is bypassed then the correct number of copies should be printed automatically.

Hidden Device

From 4.621 you can also set or clear the "hidden device" property for individual DFPRINT devices. This is also done in the Configure POD dialog.


Keyboard Customisation

The keyboard customisation option allows you to change how keys on your keyboard behave in DP4 applications. For example the default userdata.sys for Windows maps Shift+F1 to F11, so the keys behave identically in DP4 applications, and you might want to change this to enable the keys to behave differently. You can even reprogram most alphanumeric keys - you might want to do this if you were designing something like a restaurant system, where special keyboards are used and alpha keys are not required.

Background

In order to understand the keyboard customisation facility it will help to know a little about how DP4 treats the keyboard. In what follows a normal key means any kind of key that is marked with one or more symbols that the user expects to appear on screen when he presses the key, and a grey key denotes any kind of key which normally has a special function. (At one time PC keyboards typically used different colours for the two types of key). Modifier keys such as Ctrl,Shift, Alt do not generate keystrokes themselves, but change the values generated by other keys.

Real keyboards can have keys that behave in a wide variety of ways: for example function keys on terminals, may generate strings of several characters rather than a single value. On Windows and some other systems, reading the keyboard accurately involves asking the operating system whether the various modifier keys are pressed or not, as well as just reading a raw key value.

However the terminal or operating system behaves, DP4 arranges matters so that at the lowest level all keystrokes are mapped into a value between 0 and 255 in which all "normal" keys appear in their natural position (i.e. their ASCII value). Grey keys are mapped into values of 128 and above, using a scheme that allows for as many different keystrokes as possible (i.e. at this stage Shift+F1 and F11 are mapped to different values).

Next the value generated by this process is put through the key translate table stored in USERDATA.SYS, which the Keyboard Customisation facility changes. At this stage the two different values that are generated by pressing Shift+F1 and F11 are both mapped into the value that means F11 to DP4, unless you have already changed the key translate table.

However, not all keys are actually translated: in particular the upper 128 values are only translated if they were generated by some kind of grey key. Without this it would be impossible for DP4 to support both function keys and accented characters or other keys that generate values outside the 7-bit ASCII range (such as the £ or € currency symbols). So although you can reprogram alpha keys in the lower half of the table to generate function key values, you cannot do the same for accented characters. (This has implications for OSDIs that handle special keyboards on behalf of DP4)

Also Alt+key combinations are not put through the translate table. They are hard-translated to values that DP4 recognises as an Alt+key value.

In fact DP4 translates all input from whatever source, into some kind of virtual keystroke. Only the first 128 values, and Grey versions of values in the second 128 values can be translated.

Using the Customisation Option

When you select keyboard customisation, a menu is displayed from which you can choose either option 1: Display current function key settings (i.e. display the keyboard translate table), or option 2: Change current function key settings to change it.

Keys that have some kind of "edit" function are displayed with a three letter code. When you choose option 2 the screen that is displayed will explain that these codes mean.

To modify keys, proceed as follows:

  1. Press a key that you want to modify.

  2. Use the menu that appears to select the modified value you want the key to generate (for Other ASCII value and Other Function key you will also need to enter the value afterwards).

  3. Repeat steps 1 and 2 as often as necessary, and then press Q to exit (not Escape - that will allow you to reprogram the Esc key). You may want to choose option 1 to verify that you have made the changes you intend and no others. Then choose Exit, when you will be prompted to save or abandon your changes. It is probably a good idea to only make a few changes at a time, and then to check that your useradta.sys is still usable.

DFSETUP will normally prevent alphanumeric keys from being reprogrammed - if you really want to reassign an alphanumeric key, run DFSETUP with the -anykey command tail. To reprogram Q you will need to use the -Q x option, where x is some other alpha key.

Limitations

Although it is obvious, it can be frustrating: if two keys on the keyboard behave identically at the level on which DP4's "raw" keyboard functionality works, it is impossible for them to be treated separately. For example Ctrl+H and Backspace generate the same low level value (8) and therefore have to be mapped to the same functionality in DP4.

Some operating systems completely take over certain keystrokes. For example the F10 key on Windows has a special significance which means that it cannot be used in DP4 like other function keys. It would be very difficult, and probably wrong, for DP4 to try to force F10 into behaving like an ordinary key, because users who are familiar with Windows will expect DP4 programs to behave in the same way with F10 as other applications. However this type of behaviour can be very annoying if you are porting a system from one environment to another because it means users familiar with the old way of doing thisngs need a certain amount of retraining.

Keying convention

Depending on your keying convention the behaviour of the "Tab" and "Enter" keys is interchanged to some extent. However this is not achieved through a change to the translate table. Instead there is a real change in what DP4 does with keys that are mapped to "Tab" or "Enter".


Accessing Devices from DP4 Applications

This section is intended to show you how to manipulate devices from a DP4 applications, written in QA Build or C. In either case functionality is available for the following:

  • Device Group initialisation

  • POD initialisation

  • POD output

  • POD status retrieval

  • Selecting one or more input devices from which input is allowed

  • Reading input data

  • Identifying which device returned the data, if more than one input device is enabled

Running without the OSDI

The DP4 terminal manager will continue to run applications that require an OSDI even when the OSDI is not or cannot be loaded. Obviously, how well the application runs will depend on what the OSDI was needed for. Typically applications can run without the OSDI as long as they do not need particular data to be returned by calls to status functions. Application programmers normally try to arrange for their programs to run without the OSDI present as this is often very convenient for testing.

If the OSDI does not exist or cannot be loaded for whatever reason, DP4 system warning 118 will be displayed and the program will run without the OSDI.


Accessing an OSDI from QA Build

This section contains descriptions and information on the following:

  • Selecting a Device Group (QAB)
  • Selecting a POD (QAB)
  • Using a POD (QAB)
  • Retrieving a POD Status (QAB)
  • Enabling Input Devices (QAB)
  • Retrieving the Input Source (QAB)
  • Error Handling (QAB)


Selecting a Device Group (QAB )

Before your application can access any of the PODs or input devices set up for the Device Group, the initialisation POD must be selected. Although Device Groups cannot be directly selected, selecting an initialisation POD is equivalent to selecting the Device Group as a whole.

A program can only have one device group open at a time (for example a POS program may occasionally want to print to a regular DP4 printer instead of a special device based printer such as a journal). This can cause a few problems. There are two ways round this difficulty:

  • You may be able to put report into a separate QAB program, which can have its own devices. When you call this program with a %call procedure, QAB will manage the transition between devices both when the report starts and when you return to the calling program.
  • You can use the second method of selecting device groups described below rather than the first.

There are two places in a QA Build program where you can select a Device Group:

  1. Selecting the Device Group in the Program Header

    The effect of selecting the Device Group in the program header is to fix the choice of Device Group for the whole of program execution. When writing the rest of your program you will only be able to select PODs and input devices owned by the selected Device Group. Note that in a program called from another QA Build program, the Device Group is set to that specified in the called program's header. However, if the called program is an overlay, the Device Group remains set to that of the calling program. You must be careful when selecting PODs in an overlay.

    To set the Device Group in the program header:

    1. From the program editing menu select Summary
    2. Answer Yes to Initialise printer/output file?

      Here is an example Summary screen for the DP4 Till program:

    3. Make any other selections you require in the program summary, then save and return to the program editing menu. Select Output devices
    4. Make sure that the printer option is set to Yes, then select your Device Group. You will be offered the range of available initialisation PODs, that is, PODs that map to CODs defined as '1'. In our example, one of these will be TILL_INIT, the initialisation POD for the DP4 Till. Here is the Output destination screen for the example program:

      You can see that the selected POD is TILL_INIT, which we defined as belonging to COD '1' for the DP4 Till.

  2. Selecting the Device Group in a Procedure Body

    The effect of selecting the Device Group in a procedure body is that you can use more than one Device Group in a program. The restriction in this case is that only subsidiary procedures of the procedure in which you did the selection will be able to access the PODs and input devices of that Device Group.

    To set the Device Group:

    1. You must answer No to Initialise printer/output file ? in the program Summary menu as described above.
    2. Enter the procedure tree and select your procedure
    3. Choose Body editing then Procedure overview
    4. Set Procedure type to Device select/print

    The input devices and PODs owned by the Device Group are specified at procedure level, as described on the next few pages.


Selecting a POD (QAB)

If a Procedure type is set to Sub device output, a call will be made on the OSDI to initialise the POD when the procedure is activated.

To select a POD:

  1. Call up the Program editing functions menu and select the Program flow diagram

  2. Start a new procedure or edit an existing one. Choose Body editing

  3. Select Procedure overview

  4. Set the Procedure type to Sub device output

  5. Select the POD to which you want to send input. Pressing Ctrl+P will generate a list of the PODs available for the active Device Group. Here is the overview for an example procedure that selects the journal printer:

As you can see, PODs are referred to by the names you gave them in DFSETUP. When this procedure is activated during program execution both the COD mnemonic ('P' in this case) and the parameter string (`J' 0x0a for our 12-point text journal) that together define this POD are passed to the OSDI.


Using a POD (QAB)

If procedure type is set to Sub device output then any output text generated by that procedure will be sent to the selected POD. The text may be static text on a map, or a local field previously filled with data, or a combination of static text and field data.

Output can also be sent to a POD if the procedure type is set to Print and a POD has been selected by a parent procedure. Obviously your program must be sure what PODs may have been previously selected when the procedure is activated.


Retrieving a POD Status (QAB)

The status of a POD is retrieved when DEVICE_STATUS() is called from a condition or formula. A POD must have been selected prior to the DEVICE_STATUS() call.

For example, the DP4 Till application may call DEVICE_STATUS() for the Till drawer from a formula. In this way a local field can be set up to reflect the value of the drawer status:

In the example shown, the local field is set to the value "Open" or "Closed" depending on the status of the drawer.


Enabling Input Devices (QAB)

Input devices are specified as the components of a bit mask, as described in Input Devices of a Device Group. You select one or more of the input values owned by your Device Group in a procedure body.

From the Body editing menu select the Procedure overview. When you are prompted for Input subdevice you may enter a value specifying your input device(s). Pressing Ctrl+P will generate a list of the available input devices that belong to your Device Group.

Procedures used to access the DP4 Till would normally select input device 1 at this point (the keyboard). However, some procedures may expect input from the scanner. These procedures would select the value 2, as shown in the following figure.

Note that it is possible to select multiple input sources. For example, 3 specifies keyboard and scanner. Note that the keyboard is normally defined as input device 0. It only needs to be defined as some other value if you wish to combine input from other sources.


Retrieving the Input Source (QAB)

When input is returned, the variable ip_source is set to indicate which input subdevice was used provided multiple input sources are possible. The value of ip_source corresponds to the specific number of the input subdevice. Multiple input sources are defined with a mask, this value relates to one element in that mask.

QAB does not not ask for the input source if only one input device is permitted.


Error Handling (QAB )

An OSDI returns error codes to application programs in the variable print_error. This is zero to indicate no error, or greater than zero if an error has occurred. Other than this, the meaning of print_error values is device-specific and should be described in the manual which accompanies your specific OSDI.

The action your QA Build program takes when print_error is greater than zero depends on the value of another variable: pr_error_mode.

Value of pr_error_mode | Interpretation
0 (default) | Report error and terminate program
1 | Report error and continue program
2 | Ignore error
3 | Use "hot key" error handling

It is possible to use QAB "hot key" processing as a means of handling errors from the OSDI in QAB. This method is popular as it allows error handling to be localised within the QAB application. The scheme is that if you set pr_error_mode to 3 and install a hot key function then if the OSDI returns an error the hot key function will be called and will select the option defined by the value of the qpr_hotkey variable (initially this defaults to 99). This option can then examine the value of the print_error variable and take appropriate action.

This option is enabled by the QAB libraries replacing the standard dvc_check() functionality.


Accessing an OSDI from DP4 C Program

An OSDI can be accessed using the C functions in the standard DP4 C library. The interface is functionally similar to QA Build. A small example program is supplied showing how the calls may be used to access the DP4 Till.

Where a POD is passed as a function parameter, you must pass the logical device number as allocated to you by the system (in DFSETUP). For convenience, you can use a #define with an equivalent POD name to that chosen in DFSETUP. You can achieve this conveniently by generating a header file from DFSETUP containing the device numbers defined as POD names, and include this file in your C source. To generate a device header file:

  1. Choose Printer and device set up... from the DFSETUP menu
  2. Choose Parameterised output device (POD) configuration...
  3. Choose Generate POD names

You will be prompted for a header file name. Here is an extract from an example device header file for the DP4 Till. Note that the device numbers here may differ from your definitions if you have set up a DP4 Till. DP4 uses sequential numbering for devices, and yours may differ, depending on existing device definitions on your system database. You can get control over the numbers assigned to PODS (and Device Groups) by running DFSETUP with the -number command tail.

#define SLIP_PRINTER  10082  /* Slip printer */
#define JOURNAL_PRINTER  10083 /* Journal printer*/
#define TILL_ID  10084 /* Till ID */
#define TILL_DRAWER  10085 /* Till drawer */
#define TILL_INIT  10086 /* Example till for OSDI doc */


Selecting a Device Group (C Program)

You should recall that DFSETUP insists that a POD based on COD '1' is defined for any Device Group (See device definition). This is the initialisation POD and you must use it to select a Device Group, as follows:

int dvc_choose(int device_flags, int POD,char * filename,int print_width)

The dvc_choose() function initialises the Device Group. The parameters are:

  1. device_flags Selects a list of devices that will receive subsequent printer output; any combination of the screen printer and file can be selected

  2. POD Selects the POD (which should be the initialisation POD)

  3. filename If FILE has been selected in 'device_flags' then this parameter will be the address of the filename. The file output will be formatted in the style selected by the POD

  4. print_width This parameter specifies a fixed print width and is only used if the flag FIXED_WIDTH is passed as a device flag

    You must call dvc_choose() or the similar function dvc_open() (which takes the same parameters but is non interactive) to select a Device Group (via the initialisation POD) before you can use a POD or input device belonging to that Device Group.


Selecting a POD (C Programs)

Output will only be routed to a POD following a call to dvc_choose() or dvc_open(). Once this is done you may switch between PODs belonging to the selected Device Group using the following call:

int dvc_set_subdevice(int POD)

This function selects and initialises the POD, specified in numeric form. (You can use DFSETUP to generate appropriate defined constants as described in Accessing an OSDI from DP4 C Programs.)


Output to a POD (C Program)

Use the DP4 print functions to send output (see the example at the end of this section).


Retrieving a POD Status (C Program)

The following routine may be used to retrieve the status of the currently selected POD:

int dvc_get_status(char * buffer)

This function puts a null-terminated character string describing the status of the currently selected POD at the address specified by the parameter. The value returned is the length of this string. The buffer passed should be large enough to contain a string of 255 characters (unless you are sure the OSDI will not return such a long string).

An OSDI must not return more than 255 characters to the DP4 terminal manager when the corresponding function is called. The status information must not contain binary zeros. Currently QAB may not handle more than 128 characters correctly.


Enabling Input Devices (C Program)

Enabling Input Devices (C Programs)

Input devices are specified as the components of a bit mask, as described in Input Devices of a Device Group. The following function activates input devices:

void dvc_set_inp_mask(unsigned device_mask)

This function enables and disables input devices. Each input device corresponds to one bit in the device mask. Input device 0 corresponds to the keyboard.

For example if KEYBOARD=1,SCANNER=2 and MSR=4 then an input device mask of 3 enables KEYBOARD and SCANNER but disables MSR.

It is good practice to follow the example of PROGRUN and explicitly enable input before each field, and to set the input mask to 0 again once input in a field is completed. This makes it much easier for the OSDI to be sure that data from devices such as bar code readers is being read correctly.


Retrieving the Input Source (C Program)

The following function returns the integer value of the last input source. For example, if the last input was from the keyboard, 1 is returned.

int dvc_get_inp_source(void)


Error Handling (C Program)

Error handling is similar to that described for QA Build. The two variables pr_err_mode and print_error will be available to you from the DP4 header files - you do not need to declare them explicitly.

Their meaning is the same as that described in the error handling section for QA Build.

You can extend the range of pr_err_mode. The DP4 function check_printer() is called whenever an output call has been completed from an application. You can trap any value of pr_err_mode by supplying this function in your application. For example:

void check_printer()
{
  if (print_error && pr_err_mode == 3)
    take_action();
}

This function can therefore act as an exception handler for errors arising during output.


Example C Application

The following is a simple example DP4 C application that uses some of the PODs from the DP4 Till. It initialises the DP4 Till, sends output to the journal printer, requests the status of the Till drawer and finally sets input to the scanner. Further information is given in the code comments:

This program is an example only - it would be very bad practice to use hard coded messages in a real program.

/* DP4 C LIBMAKE definitions :
#db time2
#c
#end
*/

#include "dp4.h"
#include "tillapp.h"
#include "devices.h" /* Device definition header file from DFSETUP */

/* Input device mask values */
#define INP_KEYBOARD 1
#define INP_SCANNER  2

/* Map numbers (map definitions not shown )*/
#define M_TEXT_REQUEST  6
#define M_TEXT_PRINT    7
#define M_DRAWER_STATUS 8
#define M_WARNING       10

/* prototypes */
void text_out(char *);
void request_user_text(char *);

/**/

void process()
{
  char ptext[80];
  char status_str[20];
  char pause_char;

  pr_err_mode = 2;   /* Ignore errors and continue */
  map_load("TILLAPP");

  /* Initialise device */
  dvc_choose(PRINTER+NO_SELECT, TILL_INIT, "",     "");

  /* Print string to journal */
  dvc_set_subdevice(JOURNAL_PRINTER);
  if (print_error)
  {
    map_draw(M_WARNING, ERRORMAP);
    show_c(M_WARNING, 1, "Error initialising journal    printer");
  }
  else
    text_out("Hallo journal");

  /* Check status of drawer */
  dvc_set_subdevice(TILL_DRAWER);
  dvc_get_status(status_str);
  /* Display status */
  map_draw(M_DRAWER_STATUS, DATAMAP);

  if (status_str[0] == '1')
    show_c(M_DRAWER_STATUS, 1, "Open");
  else if (status_str[0] == '0')
    show_c(M_DRAWER_STATUS, 1, "Closed");
  askf_c(M_DRAWER_STATUS, 2, &pause_char);
  map_clear(M_DRAWER_STATUS);

  /* Get some scanner input */
  dvc_set_inp_mask(INP_SCANNER+INP_KEYBOARD);
  request_user_text(ptext);
  text_out(ptext);
}

/**/

void request_user_text(char *text)
{
  /* Get text from user */
  map_draw(M_TEXT_REQUEST, DATAMAP);
  askf_c(M_TEXT_REQUEST, 1, text);
  map_clear(M_TEXT_REQUEST);
}

/**/

void text_out(char *text)
{
  /* Output text - used to output to current POD */
  form(M_TEXT_PRINT);
  prn_c(M_TEXT_PRINT, 1, text);
  prn_print(FALSE);
  if (print_error)
  {
    map_draw(M_WARNING, ERRORMAP);
    show_c(M_WARNING, 1, "Error printing");
  }
}


Writing an OSDI

Itim Technology Solutions provides an OSDI pack for each of the operating systems it supports. Each OSDI pack contains a number of source files, written in the C programming language. After user modification these files are used to build an OSDI. The OSDI receives device calls from a DP4 application, via the DP4 Terminal Manager.

The OSDI source code can be broadly split into two parts :

  • Code which provides an interface between the OSDI program and the DP4 Terminal Manager. This code is not intended for user alteration. It is largely operating system dependent and implements the DP4 protocol for Terminal Manager-OSDI communication. It is supplied in object code format so that you can link it with the OSDI module that you modify - the source is provided for information purposes only.

  • Code to be written by you which provides the interface to the specific hardware that you are accessing. DP4 provides a 'shell' of empty functions in one of the OSDI modules, which you should complete with device-specific code according to the guidelines laid out in this section. The structure of this code is introduced here.

The DP4 Terminal Manager interface code is supplied pre-compiled. After you have modified and compiled the device-specific code it must be linked to produce an executable OSDI.

All output destinations in DP4 are treated as printers, in that any data to be output to them is "printed" from a DP4 DP4 application. This definition is principally one of terminology - you may access any sort of device. Data output to that device may not necessarily be physically printed. For example, you may be using a POD to send control sequences to a robot arm. The control sequences are viewed as printed text by DP4 even though they are not physically printed.


Structure of the OSDI

In every OSDI pack the file DIPx.C is provided, where x is the DP4 operating system code (w=Windows, 2 = MS-DOS, 3 = Unix/Linux, 5 = FlexOS, 7 = OS/2). DIPx.C provides the code for the DP4 Terminal Manager interface. You do not need to understand or modify this code. For some operating systems other "private" source files may be supplied. In all cases these files will be supplied pre-compiled (see the section on compiling and linking the OSDI for detail on compilers).

For each operating system DP4 also supplies a file containing user-modifiable functions. This file is named according to the operating system as follows:

  • DIPWUSER.C for Windows (16 and 32 bit)
  • DIPUNIX.C for Unix or Linux
  • DIPMSDOS.C for MS-DOS
  • DIPOS2.C for OS/2
  • DIPFLEX.C for FlexOS

These modules contain some example code which differs between operating systems. However, except for Windows, they all contain the following functions, which you may modify in order to interface between a DP4 application and your Device Group:

  • prg_start()
  • prg_stop()
  • prn_select()
  • prn_parameters()
  • prn_print()
  • prn_status()
  • prn_deselect()
  • inp_start()
  • inp_restart()
  • inp_stop()
  • inp_task()

The Windows OSDI contains different functions and is dealt with here.

In the following sections, these user-modifiable functions are described. Example modifications designed to access the DP4 Till are presented for some cases. The examples match the example definitions from the section about DFSETUP, and also the examples in the application programming section.

The DP4 Till code shown is incomplete - no operating-system dependent code is shown. Such functions would make specific calls on the DP4 Till hardware. You could implement them in a simple fashion by designing a Till simulator. In that case all the Till hardware functions (status requests, output and so on) would result in a descriptive output on your terminal.


How OSDI Functions Match Application Calls

The following table shows the user-modifiable OSDI functions that are triggered when device calls are made from a DP4 C application.

DP4 C Application Call

OSDI Function

dvc_choose()

 prg_start()
prn_select()
(prn_parameters())

dvc_close()

 prg_stop()

dvc_set_subdevice()

 prn_select()
(prn_parameters())

dvc_get_status()

prn_status()

prn_print() (after calls to DP4 functions such as form(), or prn_c() etc.)

prn_print()

dvc_set_inp_mask()

 inp_start() or inp_restart()

The following sections describe the areas of the OSDI that you may modify to handle Device Groups and their constituent PODs and input devices.


Starting and Stopping the System

Two functions are available for you : prg_start() and prg_stop().

prg_start()

This is called once at the start of the application and should do whatever initialisation is required to be done once only, for example setting baud rates on serial ports. The standard DP4 functions tail_read() and tail_get_option() may be used to read the command tails passed to the OSDI program.

prg_stop()

This is called once at the end of the application and your code should do whatever tidying up is required for your Device Group.

Under MS-DOS there are two additional startup/shutdown functions:

prg_load()

This is called once when the OSDI program is loaded. Note that the OSDI program will execute the operating system terminate-and-stay-resident function after calling this function.

prg_unload()

This is called once when the OSDI program removes itself from memory.


Handling Input Devices

The individual input devices are identified by values corresponding to powers of 2 (1, 2, 4, 8...). Combinations of input device are specified by adding together these device numbers. Input device number 1 must be implemented and must correspond to the keyboard (there must always be some way for the user to interact with DP4 - for example the application must be able to respond to error messages). Note that if you have not defined any input devices in DFSETUP, the OSDI will automatically use input device 1.

Other input devices may be implemented as required. The mnemonics INP_KEYBOARD, INP_MSR and so on are defined for input device numbers in DIP.H.

Much of the time an application may specify a zero device input mask. In this case the OSDI should enable the keyboard. However, the DP4 terminal manager will expect the input source to be returned as 0 if the device input mask is 0 and 1 if it is 1. It will discard input not from the correct source. This has been known to cause problems in the past.

The Five Input Functions

Input is handled by four or five functions, which you may modify in your OSDI:

  • inp_start()
  • inp_restart()
  • inp_task() (which can in turn call inp_idle())
  • inp_stop()


inp_start()

This is called whenever the application signals that it wishes to start receiving input from a particular input device (how calls from applications are made is covered in the section on Application Programming). The relevant input device numbers are passed to this function as an argument. Thus, when the application starts using input device n, this function is called with n as the argument. The value of n is the value of the input device bit mask selected in the application.

inp_start() should do whatever initialisation is required to start accepting input. For example, under FlexOS it will typically start an asynchronous read on the appropriate device file. Initialisation which is required only once (such as setting baud rates) should be done in prg_start() or in the initialisation of output device PRN_INIT (see Definitions and Declarations for this constant).

As an example, for our DP4 Till inp_start() only takes action when it detects that the input device selected is the scanner. It calls init_scanner() (operating-system dependent and so not shown) when it detects that the scanner has been selected.

 void inp_start(int device)
 {
   if (device & INP_SCANNER)
     init_scanner();
 }


inp_restart()

This is called whenever the application selects an input device which has already been selected. The relevant input device number is passed to this function as an argument. Some input devices may require re-initialisation after receiving input; for example it is common practice to disable a bar code reader after each bar code has been read and only to re-enable it when the application is ready for another bar code: this is done to avoid reading bar codes which the application will reject as invalid input. Such re-initialisation may be done in this function.


inp_stop()

This is called whenever the application wants to stop receiving input from an input device. The input device number in question is passed to this function as an argument. The function should do whatever is required to stop accepting input.

For example, under FlexOS it will typically cancel an asynchronous read on the device file. Tidying up which is required only once should be done in prg_stop.

It does not matter if it is not possible to prevent some input being obtained from the de-selected source (for example, if a read from that source is currently in progress) because the DP4 Terminal Manager will automatically throw away input from a source which is not selected.


inp_task()

This function is the central input function of the OSDI. It should take the form of an infinite loop. inp_task() should read input from whatever devices are currently selected and pass the input to the function inp_avail() (the latter is not for user-modification). The global variable inp_device indicates which devices are currently selected; it is the sum of the numbers of the selected devices. The function inp_avail() takes three arguments: the address of the data which has been read, the number of bytes read and the device number from which the data came.

If input is being obtained from more than one source, care should be taken to ensure that data from different sources is not interleaved. For example, if a character has been read from a magnetic stripe, the whole contents of the stripe should be read and presented to inp_avail(), not interleaved with any data from the keyboard. See the section on Concurrent Input for further details.

The DP4 Terminal Manager is automatically informed by inp_avail() when the source of the input changes.

In our example inp_task() has a very simple form, where first the keyboard is polled for input, then the scanner (via the serial port). No account is taken of concurrent input. For example, inp_device() could take a value (INP_KEYBOARD + INP_SCANNER). The user may start field entry from the keyboard but use the scanner before field termination. This example code would send data back to the application consisting of a mixture of input from keyboard and scanner.

/**/

void inp_task()

{
 while (1)
 {
  inp_idle();

  if (inp_device & INP_KEYBOARD)
  {
   char buffer;
   if (get_kbd_data(&buffer))
    /* send data to application */
    inp_avail((char *)&buffer, 1, INP_KEYBOARD);
   }

  if (inp_device & INP_SCANNER)
  {
   char buffer[64];

   if (get_com1_data(buffer))
    /* send data to application */
    inp_avail(buffer, strlen(buffer), INP_SCANNER);
  }
 }
}

Note that inp_avail() is called if input is successfully taken from an input device. get_kbd_data() and get_com1_data() are not shown.


Calls to inp_idle()

If inp_task() needs to wait for an event to occur, it should call the function inp_idle(). This function is provided for you. an OSDI basically consists of a perpetual loop requesting input - either within inp_task() or by repeated calls to inp_task(). Calling inp_idle() gives the OSDI a chance to deal with any other tasks which need to be performed.

Under FlexOS, for example, inp_task() should never call s_wait() (which waits for sync/asynchronous event to complete) but should instead call inp_idle() followed by s_status() to discover which events have completed (inp_idle() waits on all events); there is a call to s_wait() within the code of inp_idle(). Note that it is within the processing of inp_idle() that calls to inp_start() and inp_stop() will be made.


Multiple Input Sources and Concurrent Input

This section suggests some solutions to difficulties that are commonly experienced when multiple input sources are defined.

Applications almost always require input to be presented from an OSDI on a per field, per input device basis. In other words, for a given field, input is not interleaved from more than one input device, even when multiple input devices are defined.

For example, input may be requested for a field from both a keyboard and a bar-code reader. The application may wait for either device to start sending characters via the OSDI. However, once input begins from, say, the bar-reader, it is not acceptable for keyboard input to be presented until the current bar read is complete.

The general rule for input sequencing is that the OSDI should present complete fields from a single input device and the fields should be presented in input sequence.

This raises two questions: What is a complete field, and what defines input sequence?

Field Termination

A DP4 field is completed by one of either:

  • A sequence of displayable characters terminated with a defined terminator key (for example, a function key)
  • A fixed length sequence of displayable characters

To manage concurrent input your OSDI must know how field input is terminated - either how many characters constitute a fixed-length field or what character terminates a field. Terminators may be dynamically specified from an application. A useful way of dynamically assigning field characteristics is to define a POD to specify them. You may then print a string of terminator information to this POD.

There are problems associated with this method, notably the fact that keys may have their values reassigned under DP4. This means that the OSDI has to find out which hardware-generated key value matches a given DP4 key value. This can be achieved by reading the DP4 USERDATA.SYS file within the OSDI.

A more convenient method may be to make the application tell the OSDI when input has terminated for a field. In QA Build the OSDI is automatically informed by a call to inp_restart() when input for a given field is complete and input is commencing for another field. The same effect can be achieved from DP4 C by specifying dvc_redirect() between each field input. Your code in inp_restart() can decide where input should come from next.

Of course, your OSDI could have field-termination values hard-coded, but obviously this is a less flexible approach.

Concurrent Input Handling

Recognition of termination is only part of the solution to handling concurrent input to a field. Ensuring that field input occurs from a single input device requires your OSDI to make some decisions about how to handle the other active input devices. There are various strategies that may be adopted to manage concurrent input, and some are outlined below :

  1. In this scenario the OSDI does not accept concurrent input. It may wait for input on multiple sources, but as soon as input arrives from one input device input is discarded for all other devices until termination is reached. This approach has the advantage of simplicity, but the application may be unable to complete if termination is never reached from the currently active input device.

    In practice, it is unlikely that an input device that implicitly returns whole fields (such a bar reader) will fail to terminate. Similarly, field-termination from a keyboard is user-dependent for termination.

  2. Concurrent input is accepted by the OSDI. As soon as input is received from one source it is immediately passed to the application (by calling inp_avail()). This process continues until termination is seen by the OSDI. Meanwhile, any input that arrives for the other active input devices is buffered. After the first termination occurs the buffered data should be scanned to see whether any other sources have reached termination, and if so their data should be passed to the application.

    Note that this approach is vulnerable to the "never-terminated" scenario as in (1). Furthermore, your OSDI must buffer not only data but also an identifier for the source of that data. This is required so that the parameters for the call to inp_avail() are correct. Also, you should store some notion of the order in which input devices began to receive input.

    A further problem with this approach is that certain input devices signal to the user that data has been entered. The user may be unaware that the data has not yet passed through the OSDI. For example, if the OSDI is awaiting keyboard termination and the user scans an item with a bar reader, it is likely that the reader will emit a beep to signal scan completion. A user will typically accept the beep as a sign to continue - despite the fact that the data has not reached the application.

  3. In a similar fashion to (2), concurrent input is accepted by the OSDI. Input is buffered for each active input device. When termination is reached for a certain input device, all input buffered for that input device (and that device only) is passed to the application (by calling inp_avail()).

    This scheme gives the advantage of avoiding the never-terminated scenario above. However, it is not usually suitable for keyboard input because users typically expect to view keyboard input on their monitor as they type. They do not expect it to appear after termination.

The buffering in points 2 and 3 above could be managed in one of two ways:

Either: A separate buffer is managed for each input device that may be simultaneously active. Each buffer is identified such that the input source can be passed to inp_avail() on field termination. If a large number of input devices are available the corresponding number of buffers and associated management may provide a large overhead.

or: A single buffer is used to store all outstanding data. This requires that every piece of data in that buffer is marked with its input source. This may be an overhead but a single buffer provides an inherent way of tracking the order problem outlined in (2) above.

In any case where buffering is involved you must be aware of exhausting resources and losing data. It is usually unacceptable to lose data from an input device. This is particularly important when the user is notified by the device itself that input has occurred (for example, when a bar reader beeps the user will assume their application has received the input).

Buffering input may cause subtle problems. For example, you may choose to buffer keyboard data. The user may be prompted for a response to some input taken before the keyboard data was physically input (a previous bar code may require a price confirmation). The application could easily end up with your buffered keyboard data in response to the prompt - the keyboard data will then be discarded. The result is lost data. You must be aware of the demands your particular application may make and design a suitable protocol for handling any contingencies.

You do not have to follow any particular advice when handling input sources in your OSDI, you can, for example, interleave concurrent input for a field if you wish (although this is not recommended). The purpose of the above is to provide some advice about some of the pitfalls you may encounter when handling multiple input sources.

You should note that the OSDI (via inp_avail()) identifies the input source to the DP4 Terminal Manager. The DP4 Terminal Manager will automatically discard input from any device not currently selected by the application.


Input Buffering in POS Applications

There are special considerations when buffering input in POS-application OSDIs: to be meaningful, the input buffer should be able to contain at least 3 EANs (European Article Numbers). 5 EANs may be safer. In any case, the buffer must not contain part fields. Thus, if the buffer fills:

  • The user is warned, on completion of the last acceptable field, that further immediate input will be thrown away. This means that the warning needs to come up (maximum field length - 1) characters before the buffer is full. This may present problems if the user considers that an article has already been accepted (because, for example, a bar reader has beeped following a scan)

  • No input is accepted until there is room for a complete field. It may, in practice, be best to wait until the buffer is empty again and then prompt the user

Type-ahead

There is an option in DFSETUP to modify type-ahead behaviour (choose Colours and Miscellaneous Cosmetics then Type-ahead). Type-ahead is managed in the DP4 Terminal Manager. If type-ahead is turned on in DFSETUP the DP4 Terminal Manager may continue to gather input while the application is processing previous data. This allows the DP4 Terminal Manager to scan for certain control sequences.

Note that the type-ahead buffer has a limited size - if input to your OSDI is likely to be rapid there is a possibility of exhausting the type-ahead buffer. Subsequent input will be lost.

If you are in doubt about type-ahead, use Operating System Type Ahead in DFSETUP. It is recommended that you do this if you are using multiple input sources.


Handling Output Devices

The relationship between Device Group, CODs and PODs is described in Device Definition Hierarchy. The OSDI primarily views the world in terms of CODs. In other words, it is the COD identifier (defined by you in DFSETUP) that is passed to your OSDI code when your application utilises a POD. PODs can be distinguished in the OSDI because the parameter string that you defined in DFSETUP is also passed.

COD identifier '1' (31h) is reserved for your special initialisation COD (remember that DFSETUP insists that a Device Group always owns the initialisation COD '1'). This is automatically selected when the application chooses a Device Group, prior to producing output.

Output Functions

The OSDI provides user-modifiable functions to handle output, called:

  • prn_select()
  • prn_parameters()
  • prn_print()
  • prn_status()
  • prn_deselect()

Like input device functions, these are supplied as skeleton routines, which you modify according to the guidelines described for each function. These functions use various items declared in dip.h and described here.


Definitions and Declarations

The OSDI module that contains user-modifiable code (DIPUNIX.C, DIPMSDOS.C and so on) must contain certain definitions and declarations. Usually these are provided for you in some form. There are circumstances in which you may need to modify them, so a certain understanding of their function is useful.

You will see the following declarations in the OSDI user module:

 #define NR_PDESCS 1

 struct PDESC pdesc[NR_PDESCS];
 int nr_pdescs = NR_PDESCS;

All output from an application to a POD is passed to an OSDI as text to be "printed". The OSDI holds the text in a buffer. Furthermore the OSDI provides facilities for asynchronous printing whereby multiple lines of text (and therefore multiple buffers) are maintained.

The structure PDESC (print descriptor) is defined for you in DIP.H. It forms the basis of output data buffering and contains three fields:

 struct PDESC
 {
    char *text;
    int busy;
    int next;
 }

The OSDI will automatically store data output from the application at the address specified by text. The other two fields are discussed later in the section on asynchronous printing. The pdesc array is an array of these structures. This is how multiple lines of output text are buffered.

One of the actions your OSDI must take on Device Group initialisation is to set the text pointers to suitable addresses. To achieve this you should declare buffers appropriately and initialise the pdesc array to point at these buffers. Example code is provided, where buffers are declared as:

static char pdtext[NR_PDESCS][PRN_BUFSIZE];

The text field of each component of the pdtext array must be initialised to point to an array of characters large enough to contain the longest line which will be printed. This initialisation may be done in prg_start() . The value PRN_BUFSIZE defined in DIP.H is a safe size for the array of characters, but a smaller value may be acceptable if it is known that there is a smaller limit on the size of line which will be printed.

Whenever a line of text is passed to the OSDI program, it is automatically copied into the area pointed to by pdesc[i].text. The index value, i, will then be passed to the prn_print() function.

The mnemonics PRN_INIT, PRN_PRINTER and so on are defined for COD identifiers in DIP.H. They correspond to the CODs used by Itim Technology Solutions Chameleon.


prn_select()

This function is called whenever a POD is selected by the application. The global variable prn_device contains the number of the COD that owns the selected POD (the identifier you used for the COD in DFSETUP). prn_select() should do whatever initialisation is required for the COD. However, initialisation which is required only once (such as setting baud rates) should be done in prg_start().

Some POD definitions will include parameters. These parameter values will be passed by the application to the OSDI whenever the POD is selected. The parameters comprise a string of characters which is intended to set device characteristics and not to be printed. These parameters distinguish the various PODs that relate to a single COD. The prn_select() function should return 1 if the COD being selected expects parameters, and 0 if it does not.

In the example OSDI, none of the CODs require special initialisation in prn_select(). However, our generic printer (defined as 'P' in DFSETUP) does take parameters. We must therefore return 1 if this COD has been selected. This is the case when an application selects either the slip or journal printer PODs for output.

int prn_select(void)
 {
   switch (prn_device)
   {
     /* The till printer is the only COD to take a parameter */
     case 'P':
       return 1;

     default:
       break;
   }
   return 0;
 }

Some CODs may not expect to receive any text for printing at all, but only parameters (an example would be a COD whose function was to set parameters for an input device).


prn_parameters()

This function is called after prn_select() for any COD which expects parameters (a COD for which the call to prn_select() returned 1). The global variable prn_device contains the number of the currently selected COD.

The argument passed to this function is a null-terminated string sent by the application. It is the parameter string defined by you for the POD in DFSETUP. Remember that the application views output in terms of PODs and a POD is defined with an owner COD and a unique parameter definition. an OSDI uses COD identifiers together with POD parameter values.

The parameter string is intended to be used to control the COD and not to be printed. The interpretation of this string is entirely a matter for your code.

The string passed to prn_parameters() will end with either line-feed or carriage-return/line-feed depending on the printer characteristics set up in DFSETUP.

For our DP4 Till example the printer COD takes a parameter specifying the destination of the print text (slip or journal) and the character size:

/**/

void prn_parameters(text)

 char *text;

{
 int character_size;

 switch(prn_device)
 {
  case 'P':
   /* Parameter defines what kind of till printer - slip is default */
   till_printer = text[0] == 'J' ? JOURNAL : SLIP;

   /* Second part is character size */
    character_size = text[1];

   if (!init_printer(till_printer, character_size))
          prn_error = till_printer == JOURNAL ?
     ERR_JOURNAL_INIT : ERR_SLIP_INIT;
   break;

  default :
   break;
 }
}

Function init_printer() is operating-system dependent and therefore not shown. Note that the value of prn_error is set according to the type of initialisation failure. ERR_xxxxx are error definitions also known to the application (they are contained in an user-defined application-specific header file shared by the OSDI and application source).


prn_print()

This function is called when the application sends a line of text to the POD for printing. The global variable prn_device contains the number of the currently selected COD. The argument passed to prn_print() is the index of the entry in the pdesc[] array which points to the text to be printed.

The code of prn_print() should print or display or otherwise deal with the text. It should return the value 1 when it has finished.

In our example, only two of our COD types implement output. The printer COD takes text to be printed, either to the slip or journal. The output_text() function is operating-systems dependent and not shown. The text output to the drawer COD indicates whether the drawer should be opened or closed.

int prn_print(int index)
{
 switch (prn_device)
 {
  case 'P':
   /* Print text on appropriate printer */
   if (!output_text(till_printer, pdesc[index].text))
    prn_error = till_printer == JOURNAL ?
     ERR_JOURNAL_PRINT : ERR_SLIP_PRINT;
   return 1;

  case 'D':
   /* Output text indicates whether to open or close drawer */
   drawer_opened = (*pdesc[index].text == 'O');
   return 1;

   default:
   /* other devices not implemented for printing */
   return 1;
 }
}

If asynchronous printing is implemented, prn_print() may return the value 0 to indicate that printing has not been completed. See Asynchronous Printing for details of how this should be implemented.

The string to be printed by prn_print() will end with either line-feed or carriage-return/line-feed depending on the printer characteristics set up with DFSETUP.


prn_status()

This function is called when the application interrogates the status of the (currently selected) POD. The global variable prn_device contains the number of the currently selected COD. The function should return either 0 (if no status information is available) or the address of a null-terminated status string which will be passed back to the application. You define the contents of the status string.

The length of the string returned by prn_status(), including the terminating binary zero, should not exceed the value STS_BUFSIZE defined in dip.h.

Note that this facility is not intended as a means for the application to discover any fatal errors which may have occurred: for this see Error Handling.

In our example, two of our CODs return status information - the Drawer (status is open or shut) and the Till Identification Value (the latter could be returned from prn_select() instead).

char *prn_status(void)
{
  char till_id[10];

  switch (prn_device)
  {
    /* "1" indicates open, "0" indicates closed */
    case 'D':
      if (drawer_opened)
        return "1";
      return "0";

    case 'T':
      prn_error = get_till_id(till_id);
      if (!prn_error)
        return till_id;
      break;

    default:
      break;
  }
  return 0;
}

The code of get_till_id() is operating-system dependent and not shown.


prn_deselect()

This function is called when the application has finished using a POD and is about to select another. The global variable prn_device contains the number of the COD being deselected.


Asynchronous Printing

Some CODs may be implemented in such a way that it is possible to start printing when the function prn_print() is called, but for the printing not to have completed when this function returns. Support for this type of output is provided as follows.

The size of the pdesc[] array supplied by your user exit code should be greater than one. The number of elements in this array equals the maximum number possible of asynchronous print items. Whenever a line of text arrives to be printed, the OSDI code will select an entry from this array which does not have the busy flag set, fill it in and pass its index to prn_print().

If prn_print() returns 1 it is assumed that the line has been printed, and the OSDI will automatically reset the busy flag so that the pdesc[] entry can be re-used. If prn_print() returns 0 it is assumed that printing has started but not yet completed. The busy flag remains set and the pdesc[] entry is added to a linked list of pending print commands. All you have to do is ensure that 0 or 1 are returned appropriately. The OSDI will manage the buffer pointers and flags in the pdesc array for you.

The function prn_complete() is supplied in the OSDI. Your code must call this function when a print event has completed. Its argument is the index into the pdesc array for which printing is complete. You should periodically check whether any outstanding members of the pdesc array have completed their printing. It is sensible to do this within the code of inp_task(), as this routine is repeatedly called by the OSDI.

How you achieve this depends on the nature of your asynchronous output.

It is useful to arrange that the completion of printing wakes up the inp_task() function; for example, under FlexOS the asynchronous printing function could return an event mask for which inp_idle() will wait. inp_idle() actually waits on any event under FlexOS before returning. You can therefore set up an event mask when printing starts. This will be checked by inp_idle() thereby ensuring that inp_task() is called when the printing completes.

The code of inp_task() should search through the linked list of pending print commands. When it discovers one which has completed, it should call the function prn_complete(), passing as an argument the index of the pdesc[] entry which has finished. The prn_complete() function will reset the busy flag and remove the

pdesc[] entry from the linked list for you.

The start of the linked list of pdesc[] entries is the global variable busy_pdesc. This is the index in the pdesc[] array of the first entry in the list, or -1 if the list is empty. The field next in a pdesc[] array member contains the index of the next entry in the list, or -1 if it is the end of the list. Using indexes rather than pointers has the advantage that you may use the same indexes to index into any other array of control variables which the application code needs to maintain: see the sample code on the release disk for an example of how this can be done.

New entries to the linked list are always added at the end, so the order of the list is the same as the order in which the entries were submitted for printing. A new entry is always added to the list before the call to prn_print() (and is removed immediately afterwards if this function returns 1) so it is possible for prn_print() to re-arrange the order of the list so that the new entry appears in a different place. This might be useful if it is desired to vary the order of printing, for example in order to optimise printer head movement. Other than this, the linked list and the busy flags should not be touched by your code.

If the application wishes to print and all the pdesc[] entries are busy, the application will wait until one becomes available. You do not need to handle this contingency in your own code.

If the text to be printed is no longer required to be kept in memory after the prn_print() function has returned, then all the entries in the pdesc[] array can share a common data area for their 'text' pointers. More usually it will be necessary for the text to be kept untouched until the printing has finished, in which case each pdesc[] member must be provided with its own text buffer.

There will usually be a requirement for the application to wait at a certain point until all the asynchronous printing has completed. This can be achieved by having a (synchronous) COD which simply waits (typically in prn_select() or prn_parameter()) until the linked list of pending print commands has become empty. This waiting code must of course poll the status of the pending commands and call prn_complete() whenever one finishes; it should call inp_idle() in order to wait for an event.


Error Handling

Output Errors

The global variable prn_error may be set at any time by the output code to indicate that an error has occurred. The application can discover the value of this variable in order to test for errors. The values put in this variable are application-dependent.

If asynchronous printing is implemented, the application writer should be aware that an error reported by setting prn_error might refer not to the last line sent for printing, but to some previous line.

The application may continue to send output commands after the prn_error variable is set, until it happens to test its value. Thus the code of prn_print() might have to test prn_error before attempting to print, in case an error has already occurred, and if so to take suitable action.

The action to take if more printing is attempted after an error has occurred is obviously application-dependent, but a possible approach would be for prn_print() to return 0 to queue up the printing to be done later after the error has been corrected (see Reprinting After Errors); this approach could be taken even if asynchronous printing is not implemented.


Reprinting After Errors

Certain errors, such as complete failure of the printing hardware, are obviously fatal. However, the errors which are most likely to occur in practice during printing can typically be corrected by the user; examples would be a paper jam or running out of paper. The usual requirement when such an error occurs is that the application should detect it, prompt the user to correct it and then continue printing from the point at which it stopped.

It may be inconvenient to test for errors after every line of printing in the application, and in any case if asynchronous printing is implemented the application may have sent other lines to be printed after the line which is causing the error; these will be queued up inside the OSDI. Also, there may be a requirement for some sort of header to be printed when the error has been corrected, before the currently queued printing is resumed.

A suggested approach to this problem is as follows:

1 The application checks for print errors periodically during printing. At most N lines of print are submitted to the OSDI between each check, where N is some fixed number.

2 The OSDI is set up with more than N print buffers (the size of the pdesc[] array should be greater then N).

3 During normal operation, when no error has occurred, the OSDI code ensures that at least N+1 pdesc[] entries are left free at all times. This is done by flushing any outstanding asynchronous printing at the beginning of prn_print() whenever the number of free entries becomes too small. Note that the global variable used_pdesc contains the number of pdesc[] entries currently in use, from which the number free can be calculated.

4 When an error such as a paper jam or running out of paper occurs, the OSDI stops all asynchronous commands currently in progress but does not call prn_complete(), so that they remain in the linked list of pending commands. Also, any additional calls to prn_print() made by the application should return 0 to add the commands to the queue, but does not actually print them. Thus, at the point when the application discovers the error, there are at most N outstanding print commands in the queue, not being processed by the OSDI.

The user is then be prompted to repair the problem. If this proves impossible, the application should select a POD whose only function is to generate a prn_complete() call for each pdesc[] entry in the linked list, thereby throwing away the pending printing and releasing the pdesc[] entries for reuse. If, however, the problem is corrected by the user, the application selects another POD whose function is to step through the linked list of lines waiting to be printed and restart the printing for each.

5 Any header information to be printed before the printing is restarted is handled via a special COD which prints synchronously regardless of the setting of prn_error.

This approach to error handling has two advantages. Firstly, it is only necessary to test for errors at one point (or at most a few selected points) in the application, so the application error handling code can be made as complicated as required without it being duplicated within the code. Secondly, it provides an automatic mechanism for reprinting lines which were in the process of being printed when the error occurred.

The disadvantage is that, if the number N is under-estimated, the OSDI code might hang waiting for a free pdesc[] entry to become free, while all the pdesc[] entries were taken up with lines waiting to be printed after the error has been repaired.


Input Device Errors

No special mechanism is provided for notifying input device errors to the application; input devices provide input only. The use of prn_error is applicable to output requests only. If error handling is required for input devices (for example, a magnetic stripe reader might need to be able to inform the application that it had failed to read valid characters from a stripe) the information should be collected by defining a POD whose purpose is to collect information about the input device.

For example, the complete sequence of actions by the application in order to read a magnetic stripe might be as follows:

1 Select a POD defined with parameters that describe the stripe to be read

2 Select the stripe reader as an input source and read from it

3 Send a dummy print to the POD, which will return an error if the stripe was not correctly read


Operating System Dependencies

The OSDI has been implemented under five operating systems - Windows (all versions since 3.1), Unix, MS-DOS, FlexOS, and OS/2. Within the application programs there is no difference between the implementations.

  • MS-DOS system dependencies

  • OS/2 system dependencies

  • Unix system dependencies

  • FlexOS system dependencies


MS-DOS system dependencies

Under MS-DOS, the OSDI is implemented as a TSR (Terminate and Stay Resident) program. It intercepts INT 21 (the operating system call), and passes instructions for the NUL printer to the user exit code. The I/O control instruction (INT 21, Function 44) is used for device selection, including input devices, and returning error codes.

The inp_idle() function switches between a foreground process which is called by the DP4 Terminal Manager for input and output, and a background process which runs all the time executing inp_task() and putting characters into a circular buffer from which the application takes them as required. All stack and Program Segment Prefix (PSP) switching is handled automatically, so the OSDI code does not require any special care, and can do file handling in the usual way.

Note however that the high-level file handling functions provided by compilers may not work from a TSR; it is recommended that the DP4 function _msdos() be used.

When called from inp_task() the effect of inp_idle() is to allow the application to run; the application may then call inp_start() or inp_stop() or the prn_xxx() functions. The effect of this is that functions such as inp_start() and inp_stop() appear to inp_task() to be called during the execution of inp_idle().


Unix and Linux System Dependencies

The OSDI and DP4 Terminal Manager communicate through pipes, thus:

  • stdin (file number 0) is replaced by a result pipe, the same pipe is also allocated to file number 4.
  • A control pipe is allocated to file number 3
  • The terminal manager sends commands (in response to actions from the application such as selecting a POD etc.) to DIP3 using the control pipe. It also sends a command for every character it want to read in input functions.
  • The terminal manager gets the result of the commands, for example device status information and error information by reads on the result pipe. Keyboard reads are done using file handle 0, and other results are read from file handle 4 (though this should not matter to the OSDI).

A Unix OSDI manages communication by creating separate child processes. The first process (which we shall denote P0) forks to create a child process P1 and then goes into a loop calling inp_task(). P1 forks to create P2 and then goes into a loop waiting for messages; it is P1 which calls inp_start(), inp_stop() and the prn_xxx() functions. P2 forks again to run the application and then goes into a loop reading an intermediate pipe and copying its contents to the message queue.

If only keyboard input is required then inp_task() should consist of a loop which reads the keyboard and calls inp_avail().

If input from other devices is required, inp_start() may fork off processes to read the various devices; any such child process may call inp_avail() as required. Similarly prn_print() may fork off processes to perform asynchronous printing and such child processes may call prn_complete(). If the inp_task() process is not required (as in the example code) it should not call exit() as it is the immediate child of the shell which started the application; instead it should call pause() which will cause it to wait until the other processes terminate when it will receive SIGCLD.

The inp_idle() function is not required; for compatibility with the other operating systems a dummy function which does nothing is supplied.

Note that the global variables inp_device, prn_device and so on are only updated by the code of the P1 process and so inp_task() cannot rely on their values.

Note on Linux

The DP4 terminal manager detects whether an application is being run under an OSDI by testing the status of file handles 3 and 4. Prior to 4.522 only file handle 3 was tested. On Linux this prevented DP4 programs from being run inside the make utiltity, because on Linux this utility runs DP4 applications with file handle 3 belonging to what appears to be a pipe. Testing both handles allows DP4 programs to run from within make, but not from within a nested make that was called by make. This is really a bug in Linux as a well behaved make utility would not allow child processes to inherit its file handles. A work around for this problem is to define the environment variable DP4NODIP3, which prevents the terminal manager looking for DIP3.


FlexOS system dependencies

The OSDI for FlexOS in 4.5xx is loaded as a dynamic overlay, which is very similar to the DLLs found in Windows and OS/2. In earlier releases the FlexOS OSDI was similar to the Unix version.

The inp_idle() function executes s_wait() with a parameter of 0x7fffffffL thereby waiting for any event mask in use by the program to complete.

When called from inp_task(), inp_idle() may in turn call inp_start(), inp_stop() or any of the prn_xxx() functions.

Under FlexOS the standard input to the OSDI program is in the 16-bit character mode, and the application will expect 16-bit characters to be passed to inp_avail(). Any 8-bit characters read from sources other than the standard input must therefore be converted to 16-bit characters. The second argument to inp_avail() is the number of bytes passed to it, not the number of characters.

dip5.c defines four variables of type 'long' named stdin_filenr, stdout_filenr, stderr_filenr and stdcmd_filenr, and should use these whenever input or output is to be made using the four pre-defined file handles for standard I/O. The OSDI startup code will initialise these variables to an appropriate value which may vary from release to release.


OS/2 system dependencies

The main OSDI code starts two threads; one of these calls inp_task() and the other calls all the other inp_xxx() and prn_xxx() functions.

A common way of getting input from various devices under OS/2 is for the OSDI to start a thread for each input device, and for each of these threads to wait for input and then call inp_avail(). The threads would be started by inp_start() when the input device is first selected, and subsequently calls to DosSuspendThread() and DosResumeThread() should be made to stop and start selecting input from the device. If this approach is adopted, the main input thread running inp_task() is not required for input. It is suggested that this thread be used for waiting on asynchronous printing; if this is not implemented, inp_task() can simply call DosExit() as in the example program.

If only keyboard input is implemented then the above does not apply; inp_task() should consist of a loop which reads the keyboard and calls inp_avail().

The inp_idle() function is not required; for compatibility with the other operating systems a dummy function which does nothing is supplied.


Structure of the Windows OSDI

The Windows OSDI is unlike that provided for the other operating systems supported. There are several reasons for this, which it may be helpful to be aware of:

  • The Windows OSDI was originally designed for 16 bit Windows. In that environment all programs using a DLL share a common data area (a horrible feature mercifully removed in the 32 bit environment). This meant that the input and output management code used by the other OSDI packs could not be used in the Windows environment. In the 32 bit environment this problem does not arise, and it would be possible to use the same pattern as is used for the other OSDIs, by suitably adapting dipwuser, or completely replacing dipw.c, though this would only be worth doing if porting an existing OSDI to Windows.

  • In the Windows environment, the OSDI cannot easily take control of the keyboard, and the mouse also needs to be considered. In the Windows environment the terminal manager is responsible for managing these input devices, and cooperates with the Windows OSDI to manage information about input sources and so on.

  • The Windows OSDI may wish to communicate with the DP4 terminal manager or application in ways that are not possible on other operating systems, for example by sending it messages, or subclassing one or more windows of the application. The supplied code for the Windows OSDI pack was kept as simple as possible, so as not to obscure the interface between the terminal manager and the OSDI.

In the Windows OSDI pack the file DIPW.C provides the code for the DP4 Terminal Manager interface. You should not need to modify this code, though you may well find it helpful to understand it. It is supplied pre-compiled. However if you modify the definition of struct USER_INFO contained in dipw.h you will need to recompile it. In any case you may wish to recompile this module for the purposes of debugging.

The file DIPWUSER.C contains the modifiable functions for the Windows OSDI.


The Windows OSDI functions

Apart from the calls to DDI_Load() and DDI_Unload() , which are made when the OSDI is loaded and unloaded by Windows, all calls into the OSDI are made through the function DDI_Entry() by the DP4 terminal manager. This function has four parameters:

  1. A handle returned by dipw.c by the first call to DDI_Entry (which uses command 0)
  2. A command parameter which is a number from 0 to 7 that indicates the function being requested by the terminal manager
  3. Another integer parameter whose meaning depends on the command.
  4. A pointer to data whose meaning depends on the command.

Not all commands use the third or fourth parameters.

The commands map into the user functions described below as follows:

Commands | Function
0 | DDI_InitTask()
1 | DDI_Error()
2 | DDI_SelectSubdevice()
3 | DDI_PollInput()
4 | DDI_EnableInput()
4 & 7 | DDI_DisableInput()
5 | DDI_DvcStatus()
6 | DDI_OutputData()
7 | DDI_EndTask()

All the other values are reserved for future expansion. If you want to add custom commands into an OSDI then export another function and call that.

Details of the functions to be modified are given below:

  • int pascal DDI_Load(HINSTANCE hInst)

    This function is called by the operating system when the OSDI is loaded by the terminal manager. (N.B. In the Win32 environment it is not called again if you create a new thread in your application). This function can be used to perform any load time initialisations your OSDI requires. Note that the OSDI will always receive a call to DDI_InitTask() as soon as the terminal manager has loaded the OSDI.

  • int pascal DDI_Unload(int nExitType)

    This function is called immediately before the OSDI is unloaded by the operating system after the 16 bit terminal manager has freed DIPW.DLL. This function is not called in the 32 bit version. This was an unintentional omission, but does not seem to have caused any problems.

    The terminal manager will load and unload the OSDI each time an application opens and closes an Initialisation POD for a device group that requires the OSDI. It has been found that this can cause problems in some cases. An easy work around for the problem is to add a call to the Windows API LoadLibrary() in the application, to load the OSDI. This will prevent the OSDI from being loaded and unloaded by the terminal manager.

  • void pascal DDI_InitTask(struct USER_INFO near * user_info)

    This function is called from dipw.c when the terminal manager makes the initial call to the OSDI. This function should perform any initialisation necessary that was not performed by DDI_Load()

  • void pascal DDI_EndTask(struct USER_INFO near * user_info)

    This function is called when the terminal manager is closing the OSDI device group. It should perform any clean-up necessary. (For example unsubclassing the terminal manager main window if this has been subclassed). You should remember that both this function and the previous one may be called multiple times in the life of an application, as the application may sometimes select a device group that uses an OSDI, and sometimes an ordinary printer.

  • int pascal DDI_Error(struct USER_INFO near * user_info)

    This function is called when the DP4 terminal manager wants to know if an error has occurred. It may be called after selecting a new COD (calls to DDI_SelectSubdevice()) or calls to output data. DDI_OutputData(). It may also be called after DDI_InitTask() if the application does not immediately select a COD. The return value is the desired error code, or 0 for no error.

  • void pascal DDI_SelectSubdevice(struct USER_INFO near * user_info, int new_device)

    This function is called whenever the application selects a POD that has the Requires Device Driver flag set. The function is called whether or not the new COD that is selected by the POD is different from the previously selected COD. You can inspect the subdevice member of the user_info structure to determine if the COD is different or not.

    Any parameters passed in the COD application will be received by the DDI_OutputData() function described later on. Unlike for other operating systems it is the responsibility of the OSDI to determine what is parameter and what is "ordinary" output data.

  • int pascal DDI_EnableInput(struct USER_INFO near * user_info, int ip_device,int already_enabled)

    This function is called when the application calls dvc_set_inp_mask(). Typically applications will call dvc_set_inp_mask() before every input field for which the device_mask is not zero, and then call the function again after the input field to set the input device to 0.

    The code in DIPW.C makes one call to DDI_EnableInput() for every bit that is set in the new device_mask. For each call the already_enabled parameter will be non zero if that bit was already set (which it won't be if the application has followed the convention above). The value for ip_device is the value of the bit mask for that device, and not the number of the bit: i.e. if the application calls dvc_set_inp_mask(5) then the calls to DDI_EnableInput() will have parameters 1 and 4 for ip_device.

    The return value from DDI_EnableInput() is very important. DIPW.C "ors" all the return codes from the calls to DDI_EnableInput() together. If the resulting overall return code is non zero then whenever the terminal manager wants to request input from the user it uses a "polling" mode: it will continuously inspect the application message queue for keyboard and mouse messages, and whenever there are no messages it will call the DDI_PollInput() function to see if the OSDI has input for it. Using the polling method prevents the application from "idling" when it is waiting for input, and can degrade performance.

    The preferred method of returning input to the application from the OSDI is as follows:

    1. All calls to DDI_EnableInput() return 0. Now when the terminal manager wants input it will simply call the Windows API GetMessage() and wait for a message.

    2. The OSDI must creates thread to monitor the input devices (unless they are interrupt driven or there is some kind of system hook which makes this unnecessary). It must also call RegisterWindowMessage("DP4_DDI_IN") to discover the message number to be used for communication with the terminal manager. (This could be done in DDI_Load() or DDI_Input_Task().)

    3. If the OSDI receives input from one of the input devices it must format it into an array of short values. Each short can either be an untranslated key value, or alternatively one of the standard DP4 key codes listed in the file userdata.h. The OSDI must call PostMessage() (not SendMessage()!) specifying the following values for the parameters:

      1. hWnd : The handle of the application main window as held in the user_info structure
      2. wMsg : The number for the "DP4_DDI_IN" message as returned by the call to RegisterWindowMessage("DP4_DDI_IN")
      3. wParam : The number of "characters" in the buffer to be returned.
      4. lParam : The address of the buffer of input characters.

      Here is some information about character translation:

      • Characters in the range 0 to 127 and character 255 are normally translated according to USERDATA.SYS
      • Other characters are not translated, except that a character 0 followed by an MS-DOS scan code is translated to the character user.inp_xlat_table[0x80|scan_code].

      Having non translated keys makes it possible to insert keys whose meaning is forced: you use the file USERDATA.SYS to do this. For example you could insert GR_F(1) to insert an F1 key, which will be F1 even if F1 is normally translated to help.

    4. The buffer returned should be for a single, complete entry field if at all possible and the first three entries in the array must specify the source of the input using the following technique:

      1. The first short must contain either 0xff or the value GR_NEWDVC as defined in userdata.h
      2. The second short must contain the low order byte of the input source (regarded as an unsigned short type).
      3. The third short must contain in its low order byte the high order byte of the input source (regarded as an unsigned short).

      The following example code may be be a clearer explanation of what is required:

        buffer[0] = GR_NEWDVC;
  buffer[1] = src & 0xff;
  buffer[2] = (unsigned) src >> 8;

    In theory you could also return input to the application by posting a series of WM_CHAR messages, but in this case you run the risk of getting input from different sources muddled up with each other.

  • void pascal DDI_DisableInput(struct USER_INFO near * user_info,int ip_device)

    DDI_DisableInput() is also called as a result of application calls to dvc_set_inp_mask(). It is called once for every bit that was set in the previous input device bit mask, and which is not now set. This function may also be called when the OSDI is unloaded. Just before DDI_EndTask() is called any input devices that are still enabled will be disabled by calls to this function.

  • int pascal DDI_PollInput(struct USER_INFO near * user_info,short far * buffer, int trm_waiting)

    This function is called when the OSDI is in polling mode and the terminal manager is requesting input. The buffer should be filled using the same format as described for the "DP4_DDI_IN"message above. The return value is the number of characters in the buffer, including the initial 3 shorts used for specifying the input device.

  • int pascal DDI_DvcStatus(struct USER_INFO near * user_info,char far * buffer)

    This function is called when the application calls dvc_get_status(). It is envisaged that this information will be information about the status of the currently selected output device, but the implementor is free to return any information desired.

    The format of the data is expected to be a null terminated string, though from C this is not mandatory. Within QAB returning data with embedded nulls will cause unpredictable results. Within QAB returning more than 128 bytes of data may cause a catastrophic error such as a protection fault, but C programs can access up to 255 bytes of data.

    The return value should be the number of bytes of data copied to the buffer, not including the terminating 0.

  • void pascal DDI_OutputData(struct USER_INFO near * user_info,char far * buffer,int length)

    This function is called whenever the terminal manager wants to output data. It is used for both parameters and ordinary data. One technique for distinguishing between parameters and data is to set a flag whenever DDI_SelectSubdevice() is called. Until DDI_OuputData() receives the next newline,"\r\n" to be exact (assuming this is the new line sequence you defined for the device group) , any data passed to it is considered to be parameters. Subsequently any number of lines of output may be sent. Each line will be terminated with the newline sequence.

    The format of the data is an array of non null characters. buffer[wParam] is 0, so the data can be processed using str() functions.

    The following information applies only to the current implementation of the terminal manager: if you choose to take advantage of it you may be forced to change your code later on (though this is unlikely).

    This command may be called an arbitrary number of times in order to perform output. Typically for each line of output you will see two calls - one with all the text and one with a line separator. However if data is output into a coloured map, fields will be split into sections of identical colour separated by attribute setting strings.

Managing input sources

The terminal manager is primarily responsible for managing the keyboard and mouse in a DP4 Windows application. Nevertheless the OSDI can at least partially over-ride this using either or both of the following techniques:

  • It can subclass the terminal manager and intercept mouse and keyboard messages, modifying or discarding them as necessary. The terminal manager supplies its Window handle to the OSDI when it is called for the first time.
  • It can post additional WM_CHAR messages to the application main window. In order to simplify matters for OSDI developers it should be noted that the terminal manager always accepts WM_CHAR messages to the main window and treats them as though they were for the field that has focus. Additionally the terminal manager accepts WM_CHAR messages with wParam values outside the normal range of values (0 to 255). Other values are treated as DP4 character codes as given in userdata.h. Tbus PostMessage(hWnd,WM_CHAR,GR_F(1),0) will have the desired effect.

It is worth referring to the following pages in the non-Windows part of this section:

  • Input Buffering and Type Ahead Issues
  • Multiple Input Sources and Concurrent Input

However, the latter page does have some information which is not relevant to the Windows OSDI because of its different construction.


Compiling and Linking the OSDI

A sample OSDI is supplied, complete with source, which for most operating system will print to a serial port and accept input from the keyboard and a serial port. This sample code is only intended to illustrate the principles involved and is not intended to be a finished program.

The source code of the sample OSDI is supplied in the following files:

  • DIPW.C, DIPW.H, DIPWUSER.C: Windows version (16 and 32 bit)
  • DIP3.C, DIP.H, DIPUNIX.C: Unix or Linux version
  • DIP2.C, DIP.H, DIPMSDOS.C: MS-DOS version
  • DIP5.C, DIP.H, DIPFLEX.C: FlexOS version
  • DIP7.C, DIP.H, DIPOS2.C: OS/2 version

In each case the first two files implement the "standard" part of the OSDI. The skeleton code you should modify is in the last module. All the standard modules are supplied precompiled (possibly for several memory models), though you may recompile them if necessary. You are recommended to first ensure you can rebuild the skeleton OSDI before making any modifications to the source.

Please do not modify the source code of the above DIPx.C files. Any supplied source code is for information only. If you do encounter any problems, please report the full details directly to Itim Technology Solutions. Modifying these modules may compromise your ability to upgrade to later versions of DP4. (For example the communication between the terminal manager and the Unix DIP had to be changed for release 4.515)

When building an OSDI you will need the DP4 C programming tools for the appropriate release of DP4.


Supported Compilers

The following compilers and memory models are currently supported:

Windows (32 bit):

Visual C/C++ from version 2.1 onwards. If you are linking to third party libraries you will most likely need at least version 5.

Windows (16 bit):

Microsoft C version 6 or 8: Small, medium and large models

Unix or Linux:

Native Unix/Linux C compiler

FlexOS:

Metaware C: Small, medium, large and big models

MS-DOS

Microsoft C version 6 or 8: Small, medium and large models

Metaware C: Small, medium, large and big models

OS/2:

Microsoft C version 6: Small, medium and large models. Large model recommended


MS-DOS Compiling & Linking

MS-DOS Linking

Modify or re-write the code in dipmsdos.c (which is the sample code) and compile and link it in the usual way. The modules dip2.obj and dipint.obj should be included in the link.

The commands for compiling and linking are:

Microsoft C

Stack checking can be turned off by the -Gs option, though this is not actually necessary.

Remember to copy the appropriate versions of dip2.obj and dipint.obj from the OSDI distribution disk. Please ensure that you link with the correct Microsoft C library. For some versions of Microsoft C the C library may be called xlibcar.lib rather than xlibca.lib.

Small model:

cl -c -AS -FPa -Gs -Osi -Zelp dipmsdos.c
link dip2 dipint dipmsdos,<.EXE name>,nul,mcowns slibca/noe;

Medium model:

cl -c -AM -FPa -Gs -Osi -Zelp dipmsdos.c
link dip2 dipint dipmsdos,<.EXE name>,nul,mcownm mlibca/noe;

Large model:

cl -c -AL -FPa -Gs -Osi -Zelp dipmsdos.c
link dip2 dipint dipmsdos,<.EXE name>,nul,mcownl llibca/noe;

Metaware C

Remember to copy the appropriate versions of dip2.obj and dipint.obj from the OSDI distribution disk, and to take the appropriate version of hc.pro from the programming tools disk.

The Metaware startup code (INIT.ASM) should be modified as described in the Metaware manual in order to give a fixed stack size and use the correct DOS calls for memory allocation, as described in section 12.12 of the manual, and re-compiled and included in your link. If this is not done the OSDI will take up too much memory (the large and big model versions will take over all the available memory). Suitably-modified versions of init.obj are included on the programming tools disk for large and big models only. Alternatively, for small model only, you may link the OSDI with hcroot2.obj, which will have the same effect.

Small model:

hc dipmsdos.c
link init dip2 dipint dipmsdos,<.EXE name>,nul, hcowns hcse nas;

Medium model:

hc dipmsdos.c
link init dip2 dipint dipmsdos,<.EXE name>,nul, hcownm hcme nam;

Large model:

hc dipmsdos.c
link init dip2 dipint dipmsdos,<.EXE name>, nul, hcownl hcle nal;

Big model:

hc dipmsdos.c
link init dip2 dipint dipmsdos,<.EXE name>,nul,hcownb hcbe


Unix/Linux Compiling & Linking

Unix and Linux Compiling & Linking

Modify or re-write the code in dosunix.c (which is the sample code) and compile and link it in the usual way. The module dip3.c be compiled and dip3.o should be included in the link.

To avoid compatibility problems with the code in the Unix compiler library be careful with byte alignment issues: DP4 programs expect data to be packed, for example by specifying the compilation option -Zp1 on SCO compilers, or through the use of #pragma pack(1) in source code on Linux. However on some implementations, especially Linux, the system header files will not work if you compile with single byte alignment. It is probable that you do not need to force byte alignment when writing an OSDI. However, if you do then the following technique generally avoids any problems:

  1. Compile without using -Zp1 (where supported)
  2. Include all system header files first before datafit.h or dp4.h
  3. Specify #pragma pack(1) before dp4.h or datafit.h if required.

In 4.5xx there should not be any name-space conflicts between functions in the standard C library and the DP4 library (ccown.a). In the event that there are, and you are using the conflicting functions from the Unix library, then you should link the DP4 library before your modules. However, please contact Itim Technology Solutions in this case.

Appropriate commands for compiling and linking are:

cc -o dip3 $(CFLAGS) dip3.c dipunix.c ccown.a

A sample makefile is or should be supplied with the Unix OSDI.


FlexOS Compiling & Linking

The Metaware C Compiler must be used. Modify or re-write the code in dipflex.c (which is the sample code) and compile and link it in the usual way. The module dip5.obj should be included in the link. Remember to copy the appropriate version of dip5.obj from the dip distribution disk, and to take the appropriate version of HC.PRO from the programming tools disk. Appropriate commands for compiling and linking are:

Small model:

hc dipflex.c
link86 dip5,dipflex,hcowns.l86,hcse.l86

Medium model:

hc dipflex.c
link86 dip5,dipflex,hcownm.l86,hcme.l86

Large model:

hc dipflex.c
link86 dip5,dipflex,hcownl.l86,hcle.l86

Big model:

hc dipflex.c
link86 dip5,dipflex,hcownb.l86,hcbe.l86


OS/2 Compiling & Linking

Modify or re-write the code in dipos2.c (which is the sample code) and compile and link it in the usual way. The module dip7.obj should be included in the link.

As the OSDI program is a DLL, special command tails are required for compilation with Microsoft C, and in addition the program must be linked with a special library (and the default libraries must be omitted from the link). As the special DLL library is supplied only in large-model form it is recommended that the large model be used. Please refer to the Microsoft compiler manual for further information on using memory models.

Appropriate commands for compiling and linking are:

Microsoft C Large model:

cl -c -ALu -FPa -G3s -Osi -Zelp dipos2.c
link dip7 dipos2,dip7.dll,nul,llibcdll Os2,dip7/nod/noe;


Running the OSDI

  • Running under Windows
  • Running under Unix or Linux
  • Running under MS-DOS
  • Running under FlexOS
  • Running under OS/2


Running under MS-DOS

The OSDI program is a TSR which should be loaded before the application is run. If the command tail -REMAIN is given, the OSDI will remain permanently in memory; otherwise it will unload itself after the first application program to use it has finished.


Running under Windows

The OSDI is a DLL. It must be named DIPW32.DLL (for the 32 bit version) or DIPW.DLL (for the 16 bit version) . It will be automatically loaded by the DP4 terminal manager when it is required.


Running under Unix

The OSDI program is executed from the command line, and is in turn responsible for loading the application. The appropriate command is

DEVICE_PROGRAM -PROGNAME "command for running application"

Note that the entire command line for running the application is given, in quotes, as the value for the -PROGNAME command tail. You may specify other user-defined command tails as required.


Running under FlexOS

The OSDI must be named DIP5.OVR. It will be automatically loaded by the DP4 terminal manager when it is required.


Running under OS/2

The OSDI program is a DLL. It must be named dip7.dll and must be placed in the appropriate directory for OS/2 DLLs (as specified by the LIBPATH command in CONFIG.SYS). It will be automatically loaded by the DP4 Terminal Manager as required.


Appendix A: MS-DOS Serial Communications

Some previous versions of the OSDI were supplied with various utility functions which are not supplied with the present version.

The most serious omission is code handling interrupt-driven comms under MS-DOS. The sample code supplied for the MS-DOS version of the OSDI uses BIOS calls (to interrupt 0x14) to read the serial port; this is adequate if the serial port input is from a dumb terminal being operated by a human, or if the baud rate is very low, but will lose characters if the input is sent at high speed. The reason for this omission is that the necessary code is largely in assembler, and it is not easy to maintain assembly-language code that is compatible with all the different compilers and memory models for which the OSDI is not issued.

If interrupt-driven comms are required under MS-DOS, the program SERIALIO.COM may be loaded beneath the OSDI. This is a TSR which takes over interrupt 0x14 and replaces the bios code by interrupt-driven code. It works with COM1: unless given the command tail -2 in which case it works with COM2:

The source code for this program is supplied so that it may be modified if required; for example to do handshaking differently. The appropriate commands to re-compile this program are:

masm serialio;
link serialio;
exe2bin serialio.exe serialio.com


Appendix B :

Conversion of Existing OSDI Programs

Existing programs written for the old OSDI for MS-DOS may be converted by the following process:

1 Modify the code, if necessary, so that the keyboard is input device number 1 and re-compile

2 Compile the conversion code dipconv.C supplied with the new OSDI. The symbol KBD_ONLY should be defined if no input devices other than the keyboard are required

3 Re-link the old OSDI with dip2.obj, dipint.obj and dipconv.obj included in the link, and the old modules DRVRIVER.obj and DRVRIN21.obj omitted from the link

Other small adjustments to the code may be necessary. dipconv.C is supplied in source code form for you to modify as required.

The print-screen function is omitted from the OSDI as supplied. This is because it cannot be implemented except in the special case of a machine running MS-DOS with the screen in text mode. In this case a print-screen function can be implemented by intercepting interrupt 5 (your compiler manual may explain how to do this) and setting a flag which is then acted on within inp_task(). See dipconv.C for an example of how to do this using the Microsoft C compiler.


Appendix C :

Electronic Journal

A common requirement is for the application to display on the screen an 'electronic journal' which is formatted by using the printing facilities of the DP4 Terminal Manager and OSDI but which is displayed using the output functions (such as map_draw() and show_c()) and which can be scrolled by pressing cursor keys.

A possible way to implement this would be for the application to print to an electronic journal POD implemented in the OSDI, which stores its output in a file; when this is to be displayed on the screen, the application selects input from an electronic journal input device which then echoes the file contents back to an input field in the appropriate place on the screen.


Glossary

Component Output Device (COD) Generic output component of the Device Group. Provides basis of PODs

Device Driver Interface(DDI) An old name for the interface between the physical device and a DP4 application. Created with an OSDI by a DP4 device developer

Device Group Corresponds to the physical device

Device Integration Pack (DIP) Old name for the OSDI Developer Pack used to construct an OSDI

Initialisation device A special COD that is mandatory for any Device Group. It consists of a COD defined with identifier '1' and must have a corresponding POD. This initialisation POD is used to select a Device Group from an application

Input device A component of the Device Group that provides input. For example may be a keyboard, barcode or card-reader

OSDI. Alleged to stand for Open System Driver Interface. The OSDI pack is the set of source code components used to integrate support for special devices into DP4 applications. The component built with this, the driver or device interface is also now known as an OSDI

Parameterised Output Device (POD) Specific output component of the Device Group. Accessible from a DP4 DP4 application


Apology

An Apology

Once upon a time we used terms like physical device, subdevice, and logical device, which most people seemed to understand without too much difficulty. However, our then technical director decided these terms were potentially ambiguous, and possibly inaccurate, and came up with the "improved" terms of Device Group, Component Output Device, and best of all, Parameterised Output Device. The two latter terms are too much of a mouthful to say or write more than a few time, and so throughout this manual you will see the abbreviations COD and POD.

You may well find it helpful to think of PODs as "logical" devices, (and to think of CODs as little as possible).

For many years we called the piece of software described in this manual the DIP, short for Device Integration Pack. Using the DIP you wrote a DDI (Device Driver Interface). Then someone decided that TLAs (three letter acronyms) were no good, and came up with the term OSDI, which stood for...

  • Operating System Device Integration ...
    or maybe
  • Open System Driver Interface...
    or maybe
  • Open Special Device Integration...
    etc. etc.

Both these terminologies are now too well established to change again. Regarding the meaning of the term OSDI the present author has discovered that the second alternative above is the "official" interpretation, but remembering this is unlikely to be of the slightest use, and you should regard OSDI as a meaningless name (rather in the same way that in Microsoft Technology OLE originally stood for Object Linking and Embedding, but OLE is now a name and does not stand for anything.) The new terminology makes it more difficult to distinguish between the device interface you write (an OSDI), and the tools you use to create it (the OSDI pack). Any OSDI you create will most likely have to have DIP somewhere in the name anyway as on the Windows,OS/2, and FlexOS platforms the DP4 terminal manager is responsible for loading OSDIs and uses hard coded names such as DIPW32.DLL or DIP7.DLL. The provided source code still uses DIP as part of the name of all the files supplied.

Please refer to the Glossary when you are confused.


Input Devices in DP4 Applications

DP4 provides application functions to handle the following:

  • Initialise the input device (or devices)

  • Read the data

  • Identify which device returned the data, if more than one physical device is enabled