Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

DOS Pipes for Windows


SP 94: DOS Pipes for Windows

Al is the author of DOS and Windows Protected Mode and Commando Windows Programming, both published by Addison-Wesley. Al can be contacted at 310 Ivy Glen Court, League City, TX 77573 or on CompuServe at 72010,3574.


Traditional multitasking operating systems (such as UNIX) let you chain programs together using pipes. Even DOS provides a crude form of pipes. Often, you use pipes from within a program to collect data from another program. For example, instead of writing your own search routines, you could call grep from your program and read its output.

Unfortunately, Windows doesn't support pipes--not surprising since Windows doesn't support standard I/O streams. However, you can run DOS programs from a Windows application. In this article, I'll show you a way to run a DOS program (PKUNZIP, in this case), collect output as PKUNZIP creates it, and route it to a cooperating Windows program. This is different from collecting the data in a file and then reading the file. With the latter method, you must wait until the program finishes to examine its output. With a true pipe, the program's output is available even before the program ends.

Secret APIs

One of the things that makes programming for Windows unusual is the variety of APIs it presents to the programmer. Sure, the Programmer's Reference covers the basic API, but there are many extension APIs: DPMI and VDS, to name two. While all of these interfaces are documented, many are not well documented or the documentation is difficult to find.

One of these semisecret APIs is the interrupt 0x2F interface. Interrupt 0x2F is the DOS multiplex interrupt used by a variety of TSR and similar programs. Windows supports an unusual set of functions via interrupt 0x2F with AH=0x17. The most interesting function this API provides allows a DOS program to manipulate the Windows Clipboard.

Using the Clipboard from DOS

In 386 Enhanced Mode, DOS programs (running in a DOS box) can cut and paste items to the Clipboard using interrupt 0x2F. Table 1 shows the API. Note that DOS programs can only use certain Clipboard formats--in practice, anything other than text is too much trouble anyway.

Putting data on the Clipboard from DOS is simple. Just follow these steps:

  1. Make sure Windows is running in 386 Enhanced mode.
  2. Make sure Clipboard access is supported (function 0x1700).
  3. Open the Clipboard (function 0x1701).
  4. Compact the Clipboard (function 0x1709); make sure there is enough space for your data.
  5. Copy your data (function 0x1703).
  6. Close the Clipboard (function 0x1708).
Reading the Clipboard is nearly the reverse of the above:

  1. Make sure Windows is running in 386 Enhanced Mode.
  2. Make sure Clipboard access is supported.
  3. Open the Clipboard.
  4. Make sure the Clipboard has the data type you need (function 0x1704).
  5. Paste the data (function 0x1705).
  6. 6. Close the Clipboard.
If you need to pass data between a DOS program and a Windows application, the Clipboard is one way to do it. Andrew Schulman's series of articles in PC Magazine (see "References") discusses a system that allows DOS programs to call nearly any Windows API function using this technique. You can also use shared-memory buffers allocated before Windows starts (see DOS and Windows Protected Mode) inside a virtual device driver (VxD).

An Example

Using the Clipboard from DOS is similar to using it from Windows--only the method of calling the API differs. Since the Clipboard is one of the few Windows services you can access directly from a DOS program, you may need to use it to provide "live" communications between the DOS and Windows worlds.

To illustrate this, suppose you want to run a DOS program from inside Windows, collect the output, and process it in a Windows application. You could use files, but then you would need a way to synchronize the two programs. (You can't read the file in the Windows program until you finish writing to it from the DOS program.) In addition, you can't easily communicate in both directions at once.

A better solution is to collect the output in real time and pass it using the Windows Clipboard. That's exactly what CLIPSH does (see Listing One). CLIPSH hooks interrupt 0x29 (the DOS CON driver interrupt) with its int29() function. When this routine collects an entire line of output in the line array, flush_clip() passes it to the Clipboard.

Before CLIPSH sends an output line, it waits until the Clipboard contains the string CLIPSH:RDY. Then it places the line on the Clipboard preceded by a hex FF byte. This helps distinguish CLIPSH text from ordinary text. (Remember, the DOS clipboard interface doesn't support custom formats.) If CLIPSH finds CLIPSH:ABT instead of CLIPSH:RDY, it terminates the executing program.

When CLIPSH is finished, it places CLIPSH:END on the Clipboard. This is the signal for any cooperating program that the output is complete.

After CLIPSH installs int29(), it collects its command-line arguments and passes them to the standard system() call. The int29() function then collects output from the child process--not CLIPSH itself.

If you can run a program and collect its output using the DOS redirection operator, it will work with CLIPSH. Of course, CLIPSH is most useful for running programs that you ordinarily can't modify--DIR, CHKDSK, or PKUNZIP, for example. If you are using your own programs, you could just output to the Clipboard directly and bypass the interrupt 0x29 handling.

The Details

The heart of CLIPSH is flush_clip() (see Listing One). Its first duty is to switch to a private stack (the int_stack array). This requires some simple assembly code and prevents CLIPSH from overrunning the interrupted program's stack (which may be quite small).

Next, CLIPSH enables interrupts. When an interrupt occurs, such as the interrupt 0x29 that triggered flush_clip(), the CPU automatically disables further interrupts until the service routine completes. However, flush_clip() may have to wait for a cooperating Windows program to place the CLIPSH:RDY string on the Clipboard. While interrupts are disabled, no other programs can run. This causes a deadlock where CLIPSH is waiting for output from a program that can't execute. With interrupts enabled, other programs are free to run while CLIPSH is waiting.

When CLIPSH is waiting, it uses interrupt 0x2F, function 0x1680 to release its time slice to Windows. This allows other programs to execute without waiting for CLIPSH's time slice to expire.

You may need a PIF file for CLIPSH, unless your _DEFAULT.PIF file happens to contain the proper settings. You'll need the background-processing box checked, and you may want to give CLIPSH a high background priority.

Using CLIPSH

The WZIP program uses CLIPSH to execute PKUNZIP. By using file-open dialogs, WZIP provides a simple way to expand ZIP files and view their contents. Of course, PKUNZIP and CLIPSH must be in the current directory or on your path.

WZIP consists of several source files, all of which are available electronically; see "Availability," page 3. WZIP.C contains the program's main logic. CLIP.C holds some simple Clipboard functions. WINCD.C provides a dialog box that selects a directory. Since WZIP is a simple, text-based program, it uses an edit control to display text. The EWINDOW.C file makes this possible.

As you run WZIP, you'll find that if the CLIPSH window is in the foreground, everything proceeds smoothly. However, if the CLIPSH window is in the background (or hidden), WZIP runs more slowly. Although it is more aesthetic to hide the CLIPSH program, it is often too slow to be useful. It appears as though Microsoft Visual C/C++ has the same problem.

The ClipExec() function (see WZIP.C) does most of WZIP's work by placing the CLIPSH:RDY string on the Clipboard and executing CLIPSH via WinExec(). As lines appear on the Clipboard, WZIP uses wputs() to add lines to the edit control that serves as the main window (see EWINDOW.C).

The Bad News

Of course, there's no free lunch. CLIPSH has some drawbacks. First, it destroys whatever is on the Clipboard, which could annoy an unsuspecting user. (See the "References" for information on saving some Clipboard formats.) Additionally, programs that output via the BIOS or require user interaction won't work properly.

Still, many useful DOS programs will work with CLIPSH. By joining a Windows front end to a DOS workhorse, it's easy to write some surprisingly powerful programs.

Conclusion

Although it isn't perfect, the Clipboard does allow you to create a DOS/Windows pipe. You could use a variety of programs with CLIPSH to reduce the burden on your own programs: grep, PKUNZIP, DIR, or even COMMAND.COM. Of course, you can also use the DOS clipboard techniques to incorporate clipboard support in your own DOS programs.

References

Brown, Ralf, et al. PC Interrupts. Reading, MA: Addison-Wesley, 1991.

Schulman, Andrew. "Accessing the Windows API from the DOS Box." PC Magazine (August/September/October 1992).

Shaw, Richard Hale. "Save Multiple Items to the Clipboard with CLIPSTAC." PC Magazine (August 1992).

Williams, Al. DOS and Windows Protected Mode. Reading, MA: Addison-Wesley, 1993.

Williams, Al. Commando OLE 2.0 and DDE Programming. Reading, MA: Addison-Wesley, 1993.

Speeding Up Visual C++

Visual C++'s Visual Workbench apparently uses a method similar to CLIPSH to run the Microsoft compiler and tools. A cursory examination shows that a DOS program, WINTEE, runs the compiler and sends its output to Visual Workbench using some mechanism.

Like CLIPSH, when the WINTEE window is hidden (which it is by default), performance slows to a crawl. Luckily there's an undocumented way to make the WINTEE window visible and improve your compilation speed.

First use the PIF editor to modify WINTEE.PIF. You should give it an extremely high background priority. Next, start Visual Workbench with the undocumented /v switch. Now when you compile, you'll notice a WINTEE window will appear. If you want to get maximum performance, move the WINTEE window to the foreground.

I don't know if WINTEE uses the Clipboard to pass data to Visual Workbench. However, both WINTEE and CLIPSH reveal some interesting behavior about the Windows scheduler and its relationship to DOS programs.

--A.W.

Table 1: (a) The DOS-box clipboard API; (b) format codes.

(a)
Get WINOLDAP version   Input:       AX=0x1700
                       Output: if   AX=1700 then WINOLDAP does not support clipboard
                                    access, otherwise:
                                    AL=major version
                                    AH=minor version
Open Clipboard         Input:       AX=0x1701
                       Output:      AX=0 on failure
Clear Clipboard        Input:       AX=0x1702
                       Output:      AX=0 on failure
Copy to Clipboard      Input:       AX=0x1703
                                    DX=format code
                                    SI:CX=data length
                                    ES:BX=pointer to data
                       Output:      AX=0 on failure
Query Clipboard        Input:       AX=0x1704
                                    DX=format code
                       Output:      DX:AX=size of data (or zero if unavailable)
Paste from Clipboard   Input:       AX=0x1705
                                    DX=format code
                                    ES:BX=pointer to buffer
                       Output:      AX=0 on failure
Close Clipboard        Input:       AX=0x1708
                       Output:      AX=0 on failure
Compact Clipboard      Input:       AX=0x1709
                                    SI:CX=required size
                       Output:      DX:AX=Largest block available (or zero on error)
(b)
Code              Description
CF_TEXT           Ordinary text
CF_OEMTEXT        Text in machine character set
CF_BITMAP         Bitmap
CF_METAFILEPICT   Windows metafile
CF_SYLK           Excel format
CF_DIF            Data-interchange format
CF_TIFF           TIFF graphics
CF_DIB            Device-independent bitmap
CF_PALETTE        Palette for bitmap

Listing One


/* This program runs a DOS program and pipes its output
to the Windows clipboard -- Williams */
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>  /* for isspace */

#define CLIP_TEXT 1

/* Basic clipboard operations */
int clip_open(void);
int clip_close(void);
int clip_copy(int fmt,unsigned long size,void *buffer);
void *clip_paste(int fmt);
int clip_wait(void);
void flush_clip(void);

void err(int no);
void win_yield(void);

/* Track open status */
static int clip_is_open=0;
/* Storage for old interrupt vector */
void (_interrupt _far *oldint29)();

/* Interrupt function registers */
#ifdef __BORLANDC__
#define INTREGS unsigned Rbp, unsigned Rdi, unsigned Rsi, \
                unsigned Rds, unsigned Res, unsigned Rdx, \
                unsigned Rcx, unsigned Rbx, unsigned Rax
#else
#define INTREGS unsigned Res, unsigned Rds, unsigned Rdi,\
                unsigned Rsi, unsigned Rbp, unsigned Rsp,\
                unsigned Rbx, unsigned Rdx, unsigned Rcx,\
                unsigned Rax
#endif
/* Interrupt stack */
char int_stack[8192];

/* Line buffer and pointer into line */
char line[513];
int lp=1;

/* Wait for CLIPSH:RDY */
int  clip_wait()
  {
  char *hp=NULL;
  do
    {
    int i;
    win_yield();
    while (!clip_open())
      win_yield();
    hp=clip_paste(CLIP_TEXT);
    clip_close();
/* check for abort command */
    if (hp&&!strcmp(hp,"CLIPSH:ABT"))
        {
        union REGS r;
        hp=NULL;
/* Induce DOS exit call in target program */
        r.x.ax=0x4c00;
        int86(0x21,&r,&r);
        return 1;
        }
/* Wait for ready string */
    } while (!hp||(hp&&strcmp(hp,"CLIPSH:RDY")));
  return 0;
  }
/* Flush line from buffer to clipboard */
void flush_clip()
  {
  static int oldss,oldsp;
  char *stacktop=int_stack+sizeof(int_stack);
/* Switch stacks */
  _asm {
    mov oldss,ss
    mov oldsp,sp
    mov dx,stacktop
    mov ax,ds
    mov ss,ax
    mov sp,dx
    }
/* Allow future interrupts */
  _enable();
  if (!clip_wait())
    {
/* Prefix string with hex FF */
    line[0]='\xFF';
/* Terminate string with NULL byte */
    line[lp]='\0';
/* Put line on clipboard */
    while (!clip_open()) win_yield();
    clip_copy(CLIP_TEXT,strlen(line)-2,line);
    clip_close();
    }
/* reset line pointer */
   lp=1;
/* switch stack back */
   _asm {
       mov ax,oldss
       mov dx,oldsp
       mov ss,ax
       mov sp,dx
       }
  }
/* Add output character to buffer, if newline or full
   buffer, call flush_clip() */
void _far _interrupt int29(INTREGS)
  {
  if ((line[lp++]=(Rax&0xFF))=='\n'||lp>=sizeof(line))
    flush_clip();
  }
void main()
  {
  char cmdline[129],*cl=cmdline;
  char _far *pspcmd;
  int cmdlen;
/* Point to command line in PSP */
  FP_SEG(pspcmd)=_psp;
  FP_OFF(pspcmd)=0x80;
  cmdlen=*pspcmd++;
/* skip leading spaces */
  while (cmdlen&&isspace(*pspcmd))
    {
    pspcmd++;
    cmdlen--;
    }
  while (cmdlen--) *cl++=*pspcmd++;
  *cl='\0';
  printf("Processing...\n");
/* Get old interrupt 0x29 vector */
  oldint29=_dos_getvect(0x29);
/* Capture interrupt 0x29 */
  _dos_setvect(0x29,int29);
/* run program via system */
  if (system(cmdline)) err(errno);
/* Reset vector */
  _dos_setvect(0x29,oldint29);
/* Flush any remaining characters */
  printf("Done.\n");
  if (lp!=1) flush_clip();
  clip_wait();
/* Mark end of data */
  clip_open();
  clip_copy(CLIP_TEXT,10,"CLIPSH:END");
  clip_close();
  exit(0);
  }
/* common error routine */
void err(int no)
  {
  char errmsg[66];
  sprintf(errmsg,"CLIPSH:ERR=%d",no);
  clip_open();
  clip_copy(CLIP_TEXT,strlen(errmsg),errmsg);
  clip_close();
  exit(1);
  }
/* This function gets called when the program exits.
   This way, if you forget to close the clipboard,
   we will do it for you */
static void clip_exit(void)
  {
  if (clip_is_open) clip_close();
  }
/* Open clipboard */
int clip_open(void)
  {
  union REGS r;
  static int first=1;
  if (first)
    {
/* Register atexit() handler (see clip_exit() above) */
    first=0;
    atexit(clip_exit);
    }
/* Don't open it twice */
  if (clip_is_open) return 1;
  r.x.ax=0x1701;
  int86(0x2f,&r,&r);
/* Record status */
  clip_is_open=(r.x.ax!=0);
  return r.x.ax;
  }
/* Close clipboard */
int clip_close(void)
  {
  union REGS r;
/* Don't close if it isn't open */
  if (!clip_is_open) return 0;
  r.x.ax=0x1708;
  int86(0x2f,&r,&r);
/* Record status */
  clip_is_open=0;
  return r.x.ax;
  }
/* Delete clipboard contents */
int clip_clear(void)
  {
  union REGS r;
  r.x.ax=0x1702;
  int86(0x2f,&r,&r);
  return r.x.ax;
  }
/* Move data to clipboard */
int clip_copy(int fmt,unsigned long size,void *buffer)
  {
  union REGS r;
  struct SREGS s;
  unsigned long avail;
  char _far *buffp=(char _far *)buffer;
/* compact clipboard */
  r.x.ax=0x1709;
  r.x.si=size>>16;
  r.x.cx=size&0xFFFF;
  int86(0x2f,&r,&r);
  avail=r.x.dx<<16L;
  avail|=r.x.ax;
/* make sure there is enough room */
  if (avail<size) return 0;
  r.x.ax=0x1703;
  r.x.dx=fmt;
  segread(&s);
/* Move data to clipboard */
  s.es=FP_SEG(buffp);
  r.x.bx=FP_OFF(buffp);
  r.x.si=size>>16;
  r.x.cx=size&0xFFFF;
  int86x(0x2f,&r,&r,&s);
  return r.x.ax;
  }
/* Read data from clipboard */
void *clip_paste(int fmt)
  {
  union REGS r;
  struct SREGS s;
  unsigned long siz;
  void *rv, _far *rvp;
  static char sbuf[256];
  r.x.ax=0x1704;
  r.x.dx=fmt;
  int86(0x2f,&r,&r);
/* If no data available, return NULL */
  if (r.x.dx==0&&r.x.ax==0) return NULL;
  siz=r.x.dx<<16L;
  siz|=r.x.ax;
  if (siz>sizeof(sbuf)) return NULL;
  rv=sbuf;
  rvp=(void _far *)rv;
  r.x.ax=0x1705;
  r.x.dx=fmt;
  segread(&s);
  s.es=FP_SEG(rvp);
  r.x.bx=FP_OFF(rvp);
  int86x(0x2f,&r,&r,&s);
  if (r.x.ax==0)
    {
/* Error -- return NULL */
    return NULL;
    }
  return rv;
  }
/* Give time back to Windows */
void win_yield()
  {
  union REGS r;
  r.x.ax=0x1680;
  int86(0x2f,&r,&r);
  }


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.