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:
- Application starts.
- An instance of
LPTCaptureDlg is displayed.
- The main module creates an object of the
LPTDevice class.
- 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.
- A random named file is created at the mapped location (e.g. C:Prin\niaowaeu).
- The application waits and monitors the folder C:\Prin for any file changes (that it contains).
- 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.
- The application is notified for change occurred in the C:\Prin folder.
- Application waits for the print operation to finish (by detecting if the mapped file is opened by some application or not).
- 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.
- 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('/', '\\');
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();
bool Initialize(const string &sDeviceNumber, const string &sMappedFolder);
void Release();
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:
LPTDevice::LPTDevice()
{
m_sName = "";
m_sMappedFolder = "";
m_sCurrentMappedFileWithPath = "";
m_bChangeDetected = "";
}
LPTDevice::~LPTDevice()
{
DeleteFile(m_sCurrentMappedFileWithPath.c_str());
Release();
}
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;
}
}
void LPTDevice::Release()
{
if(m_sName != "") {
DefineDosDevice(DDD_REMOVE_DEFINITION, m_sName.c_str(),
m_sMappedFolder.c_str()); }
m_sName = "";
m_sMappedFolder = "";
m_sCurrentMappedFileWithPath = "";
m_bChangeDetected = "";
}
void LPTDevice::MonitorChanges()
{
if(m_sName != "") {
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()))
{
SetupDevice();
}
}
}
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);
}
string LPTDevice::GenerateUniqueFileName(const string &sMappedPath)
{
char sRandomFileName[9] = {0};
string sGeneratedFileWithPath;
do
{
sGeneratedFileWithPath = sMappedPath;
for(int i = 0; i < 8; i++)
sRandomFileName[i] = 97 + rand() % (122-97);
sGeneratedFileWithPath += string(sRandomFileName);
ifstream TestFile;
TestFile.open(sGeneratedFileWithPath.c_str());
if(!TestFile)
break;
TestFile.close();
} while(TRUE);
return sGeneratedFileWithPath;
}
bool LPTDevice::FileIsOpennedForWrite()
{
fstream TestFile;
TestFile.open(m_sCurrentMappedFileWithPath.c_str(), ios::app);
if(TestFile)
{
TestFile.close();
return false;
}
return true;
}
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