Virtual Developer Workshop: Containerized Development with Docker

Windows is really fun, mostly. But one of the things I miss about UNIX (or the IBM 1401) is the handy way it writes to a line printer. No sissy graphics, no obsequious "Page n of m" ( unless you want to write a lot of extra code), just thousands of sturdy fixed width lines of ASCII characters. Gayle Manufacturing Company (The GM in all the names and labels below) is the steel fabricating company I work for. Most of the routine reports we generate are in line printer format, nothing fancy - just the numbers. Did you ever wonder why there isn't some easy way to simply iterate through your data and print a line at a time (nostalgic sigh)?? Maybe it's just been too obvious and that's why I missed it.

I'm immersed in a project that is a redesign of a system that presently runs on UNIX. Most of the reports are fine the way they are so I want a simple way to re-create them from Windows apps. I need a line printer!! I want printf() to go to 'stdout' so I can pipe it to 'lp'. Is that so wrong? The idea is to loop throught the data in a query and print a line or two for each row of data with the odd total here and there.

Ah, well. Things have changed and there isn't a line printer so I guess I had to make my own. I designed a COM class that acts sort of like a line printer. It's written in VC++ 5 with MFC4.2, ATL and STL. Since it is a dual inteface COM component, it may be used from C++ or VBA. It makes it trivially easy to create line printer style reports and it runs under MTS. It is another example of a way to print without using a CView class. It compiles under UNICODE. The error checking is rather minimal and some of the code is probably BFI (Brute Force and Ignorance, but if I'm ignorant how can I tell?).

The COM Interface has the following methods

    write(LINE_TYPE, BSTR text)// to send lines to the print arrays 
    reset(RESET_TYPE) //to clear the print arrays 
    print(PRINT_TYPE) //to format the print arrays onto the default printer 

these properties

    font_size ( GMP_FONT_CPI)//to set the fixed width font size(10, 12, 15 cpi) 
    orientation ( GMP_ORIENTATION)  //portrait or landscape 
    title(BSTR)   //prints on the lower right corner of each page (a company standard) 
    print_heading(bool)// set to false for really plain printing 
    page_breaks(bool) //if false don't allow forced page breaks 
    punch_margin(double ) // Sets the width of the margin for 3 hole punching 

and these enumerations

     [helpstring("enum line types")] 
     typedef enum 
     } LINE_TYPE; 

     [helpstring("enum reset types")] 
     typedef enum 
     } RESET_TYPE; 

     [helpstring("enum print types")] 
     typedef enum 
     } PRINT_TYPE; 

     [helpstring("enum orientation")] 
     typedef enum 

     [helpstring("enum font cpi selection")] 
     typedef enum 
     } GMP_FONT_CPI; 

It works like this Use the usual means of getting a pointer to an IGMPrintEZ instance. I like the #import method so

        IGMPrintEZPtr p_prt; 
        HRESULT hr = p_prt.CreateInstance(__uuidof(GMPrintEZ)); 

The printer object starts out empty but just to be sure I can reset it

        p_prt->reset(GMP_RS_ALL);    // clears all the data from the arrays 

Write the heading lines to the heading line table

        // the text is a BSTR so use any means of providing one
        BSTR text = SysAllocString(L"HeadingLine 1"); 
        CString str_blankline = ""; 
        p_prt->write(GMP_LT_HEAD, text); 
          // a blank line 
        p_prt->write(GMP_LT_HEAD, str_blankline.AllocSysString()); 
                     SysAllocString(L"Column 1        Column 2  Column3")); 

You can write heading lines any time during the process. There is a separate array for the heading and the body lines. The driving program could write the heading lines AFTER processing all the body lines so that totals appear on each page heading. This opens a number of possibilities. In fact the title, orientation, and font can all be determined after the body lines have been written to the body line array, and set just prior to actually printing.

The other operation is to write the body lines

       LINE_TYPE     lt = GMP_LT_BODY; 
        while(! data.IsEOF()) 
            CString data; 
            data.Format("%25s %10d", data.item, data.quantity); 
            p_prt->write(lt, data.AllocSysString()); 

Before we print we set the title, the format to landscape and the font to 12 characters per inch

	p_prt->title = _T("Test Job");
        p_prt->orientation = GMP_LANDSCAPE; 
        p_prt->font_size = GMP_FONT_12; 

And then we print


The print object iterates through the BODY line array and prints a line for each entry in the array. The heading and footer are separated from the body by horizontal lines. The date and time print in the lower left, the page number in the center and the title in the lower right.

The main elements of the program are a pair of vectors ( vector), one for the heading lines and one for the body lines, and a print loop. Here's what the print loop code looks like

    bool GMLinePrintpage_loop() 
     // loop thru the body lines and print to the page 
             CPrintDialog pd(FALSE); 
             DOCINFO di;     // must have DOCINFO for CDCStartDoc(DOCINFO) 
             m_line = 0; 
             m_max_lines = 20; 
             m_last_body_line = m_max_lines; 
             m_line_height = LINEHEIGHT_10; 
	      // if the body lines don't contain anything just return now 
		if(0 == body_lines->size())
		    return true;

             memset(di, 0, sizeof(DOCINFO)); // make a clean start 
             di.cbSize = sizeof(DOCINFO); 
             di.lpszDocName = m_title; 
	// lazy way of getting the default printer 
	// just get all the printer defaults - no  display 
	// so this COM object can run from MTS 

             DEVMODE *dm = pd.GetDevMode(); 

	     // set orientation
             // print landscape or portrait? 
             dm->dmOrientation = m_orientation + 1; 
             // signify te presence of orientation data 
             dm->dmFields |= DM_ORIENTATION; 

             // set punch margin 
             case GMP_PORTRAIT 
                  m_top_offset = 0; 
                  m_left_offset = INCH * m_punch_margin; 
             case GMP_LANDSCAPE 
                  m_top_offset = INCH * m_punch_margin; 
                  m_left_offset = 0; 

             // create the printer device context by getting values from the 
             // printdialog and the dm structure 
             CDC dc; 
             if(! dc.CreateDC(pd.GetDriverName(), pd.GetDeviceName(), 
              pd.GetPortName(), dm)) 
                  AfxMessageBox(_T("Can't create DC in print_loop")); 
                  return false;


             // obtain the page dimensions from the Device Context 
             m_page_height = dc.GetDeviceCaps(VERTSIZE) * MM_TO_INCH; 
             m_page_width = dc.GetDeviceCaps(HORZSIZE) * MM_TO_INCH; 

             CFont *oldfont; 

             // select font and set line height 
             case GMP_FONT_12 
                  oldfont = dc.SelectObject(C12); 
                  m_line_height = LINEHEIGHT_12; 
                  m_cpi = 12; 
             case GMP_FONT_15 
                  oldfont = dc.SelectObject(C15); 
                  m_line_height = LINEHEIGHT_15; 
                  m_cpi = 15; 
             case GMP_FONT_10 
                  oldfont = dc.SelectObject(C10); 
                  m_line_height = LINEHEIGHT_10; 
                  m_cpi = 10; 

             // compute the lines per page 
             m_max_lines = -(( m_page_height - m_top_offset) / 

             // compute the last body line 
                m_last_body_line = m_max_lines - FOOTER_LINES - 
                     head_lines->size() - HEADER_SEPARATOR;
                  m_last_body_line = m_max_lines;

             // the print loop 
             // I like to use the Standard Library collections when I can. 
             vectoriterator itext; 
             m_page = 0; 
             for(itext = body_lines->begin(); 
	     	 itext < body_lines->end(); 
                  // look for a pagebreak 
                  if("@@FF" == itext->Left(4)) 
                        m_line = 0; 
                       // otherwise just ignore the page_break token
                       if (m_line >= m_last_body_line) 
                            m_line = 0; 
                       if (m_line == 0) 

		     		 (m_line++ * m_line_height) - m_top_offset,

             return true;
              return false;

There is no user interface to this object (that's a feature!!) so there is no print preview or printer dialog. This makes it it possible to install this COM class on MTS and run the printer from a process on a remote system.

Download demo project - 36.8 Kb


  • ERROR!!!!!

    Posted by Legacy on 05/29/2002 07:00am

    Originally posted by: Glen

    INK : fatal error LNK1104: cannot open file "mfcs42u.lib"

  • Automatic Form Feed

    Posted by Legacy on 04/06/2001 07:00am

    Originally posted by: G Collister

    Does anyone know of a way to override the automatic form feed when using StartPage/EndPage/StartDoc/EndDoc calls?

    I need to write a line at a time to a printer (possibly over a network and shared), without automatic page breaks.

    Any help would be appreciated,


  • another way to do it

    Posted by Legacy on 02/07/2001 08:00am

    Originally posted by: Kelly Grant

    I work with a security application that prints a line to a dot matrix printer (got one from a museum :-) each time a significant action occurs. I can't store it up and print it a page at a time, as I don't want to risk losing anything if there is a hardware failure. My solution was to open the port directly and write using low-level tools. Of course, this is not a shared printer, and the application runs stand-alone on the computer, so I don't have to be a good citizen. I'm working in Ada, so I put a Task around the actual printing in case the printer has problems; those of you using modern tools could use a thread to do the same thing.

    Just another approach.

  • customizing Paperlength

    Posted by Legacy on 02/12/2000 08:00am

    Originally posted by: Daniel Ortiz

    I have a really hard problem. I have to adjust the paperlength for printing a special-document on a dot-matrix-printer (Tractor).

    This code works on Win9X, but not on NT. Can anybody help me? It�s really urgent:

    CPrintDialog printDlg(FALSE);
    if (printDlg.DoModal() == IDCANCEL) return false;
    DEVMODE* pDevMode = (DEVMODE*)GlobalLock(printDlg.m_pd.hDevMode);
    // Change to specialformat
    pDevMode->dmPaperSize = 0;
    // values are in millimeters, because of german use
    pDevMode->dmPaperLength = 2120;
    pDevMode->dmPaperWidth = 1480;
    pDevMode->dmFields = pDevMode->dmFields | DM_PAPERSIZE | DM_PAPERLENGTH | DM_PAPERWIDTH;

    Windows NT also ignores the if you change dmFormName to a special Format, which you created in NT Print-Server-
    settings. Don�t know how to realize this. Can anybody help?

    Daniel Ortiz
    creative computing

  • Word wrap!

    Posted by Legacy on 12/18/1999 08:00am

    Originally posted by: Balaji

    Does it has the word wrap facility?

  • Line Printer?

    Posted by Legacy on 06/01/1999 07:00am

    Originally posted by: stuart

    This appears to be a page printer class , as it appears to store up each line and then print all the stored data with its headers and footers in one go.

    Have I got this right?

    Or does it really print a single line at a time to a continuous feed printer?

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date