Click here to Skip to main content
Licence CPOL
First Posted 6 Sep 2009
Views 41,771
Downloads 1,792
Bookmarked 33 times

How to Capture LPT Port(s) to Intercept DOS Print Requests

By | 20 Oct 2009 | Article
This article explains how you can capture any LPT port to intercept DOS print requests; sample code shows how to redirect data to a specified directory.

Introduction

This article will show how you can capture the LPT ports (LPT1, LPT2 ...) to intercept any data sent to a port from DOS applications in particular. While this article talks about redirecting DOS applications' print requests, it is equally capable of capturing print requests made from Windows applications.

The use of this sample could be where the users want:

  • to redirect print requests
  • to save print data to files (for future use or processing)
  • a network printer (or any device) to print files from a DOS application

By using the technique described in the article, you can easily make your DOS programs behave more flexible as far as printing is concerned.

Background

I was developing an LPT Capture utility so I tried to find ways to capture LPT ports. Monitoring and capturing (Windows) printer jobs didn't work because it was unreliable to intercept a print job that is almost to be submitted to an actual printer. So I looked up other ways to intercept even before a print job is created.

After research, I found the DefineDosDevice function in the Win32 API to be working and fulfilling my needs to create an LPT Capture tool. Neither MSDN nor (almost) any other resource on the web explain that DefineDosDevice can be used to define an LPT device and to map it to a folder. So, it took me a bit longer to figure out this way, and because of no documentation or examples available over the Internet, I decided to post this article on CodeProject.

Technical Solution

The sample is a C++, Win32/MFC dialog based application that has a dialog LPTCaptureDlg which is simple enough (see screenshot above), a class named LPTDevice that encapsulates an LPT port capture, and generates a random mapping file, a monitors mapping file, and releases resources, etc.

How It Works

Following is a high level sequence of steps that are performed to make things work:

  1. Application starts.
  2. An instance of LPTCaptureDlg is displayed.
  3. The main module creates an object of the LPTDevice class.
  4. The object is initialized with the desired LPT port number and a folder to map - for example, passing 1 and C:\Prin as parameters to animalization will start capturing the LPT1 port and will store any data received on the port to C:\Prin (rest of the steps assume that the LPTDevice object is initialized using these values.
  5. A random named file is created at the mapped location (e.g. C:Prin\niaowaeu).
  6. The application waits and monitors the folder C:\Prin for any file changes (that it contains).
  7. As soon as a print request is made (data is sent to LPT1), the defined DOS device (e.g., LPT1) automatically saves it to C:\Prin\niaowaeu - hence, a file change occurs.
  8. The application is notified for change occurred in the C:\Prin folder.
  9. Application waits for the print operation to finish (by detecting if the mapped file is opened by some application or not).
  10. The application removes the LPT device and recreates it by generating a unique new random named file for mapping at C:\Prin (e.g., C:\Prin\randomname) - That way, the old file remains stored at 'C:\Prin' that can be used for further operations and/or can be sent to another printer or destination.
  11. Go to step 6.

Using the Code

As the code is simple, I will illustrate only the notable portions of the code files on this page.

LPTCaptureDlg.cpp

Only the 'Apply' button in LPTCaptureDlg has an event defined. It takes the folder path and creates an LPTDevice object which in turn creates a DOS device and a monitoring thread for monitoring changes in the mapping file.

void CLPTCaptureDlg::OnBnClickedApply()
{
   CString LPTPath;
   GetDlgItemText(IDC_EDIT_LPT1, LPTPath);
   LPTPath.Replace('/', '\\');   //Replace forward with backslash

   if(LPTPath[LPTPath.GetLength() - 1] != '\\')
      LPTPath += "\\";   //append backslash

   bool IsCaptured =
      ((CButton*) GetDlgItem(IDC_CHECK_LPT1))->GetCheck() ==
      BST_CHECKED;

   delete m_pDevice;

   if(IsCaptured)
   {
      //You can create 2,3...9 as desired
      m_pDevice = new LPTDevice();
      if(!m_pDevice->Initialize("1", LPTPath.GetBuffer()))
      {
         delete m_pDevice;
         AfxMessageBox("Failed to initialize LPT1. ");
      }
      else if(!m_pWinThread)
      {
         CreateMonitoringThread();	//Create LPT monitoring thread
      }
   }
}

LPTDevice.h

The LPTDevice class exposes the following interface:

class LPTDevice
{
   public:
      LPTDevice();
      ~LPTDevice();

      //Initialize object to capture specified LPT port and a
      //folder to map output to.
      bool Initialize(const string &sDeviceNumber, const string &sMappedFolder);

      //Releases all captured resources
      void Release();

      //Monitor any changes, regenerate mapping file if changed
      void MonitorChanges();

      std::string GetMappedFolder();

   private:
      static string GenerateUniqueFileName(const string &sMappedFolder);
      bool SetupDevice();
      bool FileIsOpennedForWrite();

      string m_sName;
      string m_sMappedFolder;
      string m_sCurrentMappedFileWithPath;
      bool m_bChangeDetected;
      FILETIME m_LastModifiedOn;
};

LPTDevice.cpp

The LPTDevice implementation:

//Default constructor to initialize members
LPTDevice::LPTDevice()
{
	m_sName = "";
	m_sMappedFolder = "";
	m_sCurrentMappedFileWithPath = "";
	m_bChangeDetected = "";
}

//Deletes mapped (temporary) file and release
//any mappings that were created by this object
LPTDevice::~LPTDevice()
{
	DeleteFile(m_sCurrentMappedFileWithPath.c_str());

	Release();
}

//Initializes the object with a device number (1,2,3...9)
//and a folder where redirected files should be saved
bool LPTDevice::Initialize(const string &sDeviceNumber, const string &sMappedFolder)
{
	Release();

	m_sName = "LPT";
	m_sName += sDeviceNumber;
	m_sMappedFolder = sMappedFolder;

	if(SetupDevice())
	{
		return true;
	}
	else
	{
		m_sName = "";
		m_sMappedFolder = "";
		return false;
	}
}

//Releases any mappings created by the name
void LPTDevice::Release()
{
	if(m_sName != "")	//device was initialized
	{
		DefineDosDevice(DDD_REMOVE_DEFINITION, m_sName.c_str(), 
			m_sMappedFolder.c_str());	//restore previous
	}

	m_sName = "";
	m_sMappedFolder = "";
	m_sCurrentMappedFileWithPath = "";
	m_bChangeDetected = "";
}

//Called by monitoring thread to detect changes
//if change detected it calls SetupDevice
void LPTDevice::MonitorChanges()
{
	if(m_sName != "")	//device is initialized
	{
		Sleep(100);

		FILETIME ModifiedTime;
		HANDLE FileHandle = CreateFile(m_sCurrentMappedFileWithPath.c_str(),
			GENERIC_READ,
			0,
			0,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			0);

		if(FileHandle == INVALID_HANDLE_VALUE)
			return;

		GetFileTime(FileHandle, NULL, NULL, &ModifiedTime);
		DWORD FileSize = GetFileSize(FileHandle, NULL);
		CloseHandle(FileHandle);

		if((m_LastModifiedOn.dwHighDateTime != ModifiedTime.dwHighDateTime
		    || m_LastModifiedOn.dwLowDateTime != ModifiedTime.dwLowDateTime)
		    && FileSize > 0 && (!FileIsOpennedForWrite()))
		{
			//file got change
			SetupDevice();
		}
	}
}

//Generates a unique file name at currently mapped folder, creates
//new file with newly generated name and defines a DOS device
//such as LPT1 or LPT2 against MappedFolder/NewFileName
bool LPTDevice::SetupDevice()
{
	DefineDosDevice(DDD_REMOVE_DEFINITION, m_sName.c_str(), 
				m_sCurrentMappedFileWithPath.c_str());

	m_sCurrentMappedFileWithPath = GenerateUniqueFileName(m_sMappedFolder);

	HANDLE HandleToFile;

	if((HandleToFile = CreateFile(m_sCurrentMappedFileWithPath.c_str(),
		GENERIC_WRITE,
		0,
		0,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		0)) == INVALID_HANDLE_VALUE)
	{
		return false;
	}

	GetFileTime(HandleToFile, NULL, NULL, &m_LastModifiedOn);
	CloseHandle(HandleToFile);

	return (DefineDosDevice(0, m_sName.c_str(), 
		m_sCurrentMappedFileWithPath.c_str()) == TRUE);
}

//Generates a unique 8 character file name at sMappedPath (folder path)
string LPTDevice::GenerateUniqueFileName(const string &sMappedPath)
{
	//Generate a filename
	char sRandomFileName[9] = {0};
	string sGeneratedFileWithPath;

	do
	{
		sGeneratedFileWithPath = sMappedPath;

		for(int i = 0; i < 8; i++)
			sRandomFileName[i] = 97 + rand() % (122-97);	//small alphabet

		//check on disk
		sGeneratedFileWithPath += string(sRandomFileName);

		ifstream TestFile;
		TestFile.open(sGeneratedFileWithPath.c_str());

		if(!TestFile)
			break;	//bail if file does not exist already

		TestFile.close();

	} while(TRUE);

	//file name has been generated.
	return sGeneratedFileWithPath;
}

//Checks if file is opened for write by any application or not
bool LPTDevice::FileIsOpennedForWrite()
{
	fstream TestFile;

	TestFile.open(m_sCurrentMappedFileWithPath.c_str(), ios::app);

	if(TestFile)
	{
		TestFile.close();
		return false;
	}

	return true;
}

//Returns currently mapped folder
string LPTDevice::GetMappedFolder()
{
	return m_sMappedFolder;
}

Summary

This article showed how DefineDosDevice can be used to capture LPT ports. Do you have any questions, comments, suggestions, or anything else? Please keep me posted through the discussion forum below. I welcome your support, and thanks for reading! Enjoy.

History

  • 06-September-2009 - Wrote and published article
  • 14-September-2009 - Added non-MFC version (source code and demo)
  • 20-October-2009 - Fixed couple of bugs reported by members and a few code improvements

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Shams Dar

Software Developer

United Kingdom United Kingdom

Member

Shams has been programming the dumb machines since 2005. He is a Software Developer, mostly develop using C, C++ and C#.
Along with job he has done various projects independently in different domains including a couple of PC games.
His primary interests are application development & game design. C++ and C# are his favorite programming languages. He is a BS(CS).
Other than programming, his most keen interest is to spend all his free time with his wife.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 Pinmembermemcod9:17 6 Mar '12  
QuestionWinXP service to stream all LPT data to IP address and port? Pinmembermemcod9:16 6 Mar '12  
QuestionHow to setting the margin PinmemberMax++18:32 5 Feb '11  
GeneralProgram create file but printer no output PinmemberTeoh Chia Wei16:56 6 Jan '11  
GeneralRe: Program create file but printer no output PinmemberShams Dar7:33 13 Jan '11  
GeneralConvert C++ to VB.net PinmemberTeoh Chia Wei22:41 20 Dec '10  
GeneralRe: Convert C++ to VB.net PinmemberShams Dar7:35 13 Jan '11  
GeneralRun in background OR as service PinmemberMember 140963522:08 2 Jun '10  
GeneralRe: Run in background OR as service PinmemberShams Dar0:32 4 Jun '10  
GeneralRe: Run in background OR as service PinmemberMember 14096350:28 5 Jun '10  
GeneralRe: Run in background OR as service [modified] PinmemberShams Dar1:24 5 Jun '10  
GeneralRe: Run in background OR as service PinmemberMember 14096350:43 5 Jun '10  
GeneralRe: Run in background OR as service PinmemberShams Dar1:31 5 Jun '10  
GeneralNice One PinmemberJahangir Shahzad0:08 14 Apr '10  
GeneralVB.NET or C# PinmemberMichalss23:38 25 Feb '10  
GeneralRe: VB.NET or C# PinmemberShams Dar0:17 26 Feb '10  
GeneralRe: VB.NET or C# PinmemberMichalss0:48 26 Feb '10  
GeneralRe: VB.NET or C# PinmemberMichalss1:07 26 Feb '10  
GeneralRe: VB.NET or C# PinmemberShams Dar3:13 26 Feb '10  
GeneralRe: VB.NET or C# PinmemberMichalss6:46 26 Feb '10  
GeneralRe: VB.NET or C# PinmemberShams Dar0:47 4 Jun '10  
GeneralRe: VB.NET or C# Pinmembernut5594:39 19 Aug '10  
GeneralRe: VB.NET or C# PinmemberShams Dar0:25 23 Aug '10  
GeneralGreat Great work Pinmemberaryanabc8:54 15 Dec '09  
GeneralRe: Great Great work PinmemberShams Dar18:24 15 Dec '09  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 20 Oct 2009
Article Copyright 2009 by Shams Dar
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid