« November 2006 | Main | February 2007 »

December 2006 Archives

December 30, 2006

Redirecting Console Input/Output using Pipes

By Brandon W. Yuille

I wanted to create my own shell or command prompt, which I could customize (make transparent, ect). I started by using Pipes to accomplish this. Well, I ran into some road blocks when I tried to redirect applications like telnet.exe. The problem lies in the fact that telnet checks to make sure that the std handles are not being redirected via pipes. That's where I decided to give up on my little project (the main reason I started was to use my console for telnet).

With all the information I gained from attempting this, I figure may as well share it. Also, someone might be able to figure out how to fix this problem along the way. If so, please let me know!

The code provided is a basic class (CCmdWnd) that is derived from a CWnd. I would say that none of this code is anywhere near production code. But with some work, you could make it into something nice! When looking at this code, don't worry too much about how I draw my output or anything like that. The important part is how I redirect the console IO. So pay attention to: ExecuteExternal(), ReadAndHandleOutput(), and WriteToChild().

Class Declaration

// CmdWnd.h
#pragma once
// CCmdWnd

#define UWM_CONSOLEOUTPUT (WM_APP + 1)
#define UWM_CHILDEXIT (WM_APP + 2)

class CCmdWnd : public CWnd
{
DECLARE_DYNAMIC(CCmdWnd)

public:
CCmdWnd();
virtual ~CCmdWnd();

protected:
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC *pDC);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg UINT OnGetDlgCode();
afx_msg LRESULT OnConsoleOutput(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnChildExit(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()

BOOL ExecuteInternalFile(CString ExeName, CString &Response);
BOOL ExecuteExternal(CString FileName);

int GetNewLineCount(CString text);

BOOL GetPrevCommand(CString &command){
if((m_commandindex-1) >= 0){
m_commandindex--; command = m_vcommands.at(m_commandindex);
return TRUE;}
return FALSE;};
BOOL GetNextCommand(CString &command){
if((m_commandindex+1) < m_vcommands.size()){
m_commandindex++; command = m_vcommands.at(m_commandindex);
return TRUE;}
return FALSE;};
void AddCommand(CString command){
m_vcommands.push_back(command);
m_commandindex = m_vcommands.size();
};

std::vector m_vcommands;
int m_commandindex;
CString m_curcommand;
CString m_output;
CFont m_font;

static UINT ReadAndHandleOutput(LPVOID pParam);

BOOL WriteToChild(CString text);
HANDLE m_hChildProcess;
HANDLE m_hPipeRead;
HANDLE m_hPipeWrite;
};

Class Implementation

// CmdWnd.cpp : implementation file
//

#include "stdafx.h"
#include "CmdWnd.h"


// CCmdWnd

IMPLEMENT_DYNAMIC(CCmdWnd, CWnd)

CCmdWnd::CCmdWnd()
{
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
strcpy(ncm.lfCaptionFont.lfFaceName, "Calibri");
m_font.CreateFontIndirect(&ncm.lfCaptionFont);

m_commandindex = 0;
m_hChildProcess = NULL;
}

CCmdWnd::~CCmdWnd()
{
}


BEGIN_MESSAGE_MAP(CCmdWnd, CWnd)
ON_WM_CREATE()
ON_WM_PAINT()
ON_WM_ERASEBKGND()
ON_WM_DESTROY()
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_GETDLGCODE()
ON_MESSAGE(UWM_CONSOLEOUTPUT, OnConsoleOutput)
ON_MESSAGE(UWM_CHILDEXIT, OnChildExit)
END_MESSAGE_MAP()

// CCmdWnd message handlers
UINT CCmdWnd::OnGetDlgCode()
{
return DLGC_WANTTAB | DLGC_WANTARROWS | DLGC_WANTALLKEYS;
}

int CCmdWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CWnd::OnCreate(lpCreateStruct) == -1)
return -1;

return 0;
}

void CCmdWnd::OnDestroy()
{
CloseHandle(m_hChildProcess);
m_hChildProcess = NULL;

CWnd::OnDestroy();
}

BOOL CCmdWnd::OnEraseBkgnd(CDC *pDC)
{
return TRUE;
}

void CCmdWnd::OnPaint()
{
CPaintDC dc(this);

CString output;
CRect rWnd;
CRect rOutput;
GetClientRect(rWnd);
CDC bufDC;
CBitmap bufBmp;

bufDC.CreateCompatibleDC(&dc);
bufBmp.CreateCompatibleBitmap(&dc, rWnd.Width(), rWnd.Height());
CBitmap *pOldBmp = bufDC.SelectObject(&bufBmp);

bufDC.FillSolidRect(rWnd, RGB(0, 0, 0));

bufDC.SetBkMode(TRANSPARENT);
bufDC.SetTextColor(RGB(0, 0xFF, 0));
CFont *pOldFont = bufDC.SelectObject(&m_font);

// Draw all the output
rOutput.left = rWnd.left;
rOutput.right = rWnd.right;
rOutput.top = 0;
rOutput.bottom = 0;

output = m_output;
int lHeight = bufDC.GetTextExtent(output).cy;
rOutput.bottom = lHeight;
CString tmp;
for(int i = 0; i < output.GetLength(); i++)
{
tmp += output.GetAt(i);
CRect rTmp(0,0,0,0);
bufDC.DrawText(tmp, rTmp, DT_SINGLELINE | DT_CALCRECT);
if(rTmp.right > rWnd.right || tmp.Right(1) == "\n")
{
CString hold; hold = "";
if(tmp.Right(1) == "\n")
{
tmp.Replace("\n", "");
tmp.Replace("\r", "");
}
else
{
hold = tmp.Right(1);
tmp = tmp.Left(tmp.GetLength()-1);
}

tmp.Replace("\n", "");
tmp.Replace("\r", "");

bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
rOutput.top += lHeight; rOutput.bottom += lHeight;
tmp = hold;
}
}
tmp.Replace("\n", "");
tmp.Replace("\r", "");
bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
rOutput.top += lHeight; rOutput.bottom += lHeight;

if(m_hChildProcess == NULL)
{
// Draw the current command line
rOutput.left = rWnd.left;
rOutput.right = rWnd.right;

CHAR szPath[MAX_PATH]; //MAX_PATH is defined as 260
INT iLen = 0;
iLen = GetCurrentDirectory(MAX_PATH, szPath);
strcat(szPath, ">");

output = szPath;
output += m_curcommand;
if(rOutput.bottom == rOutput.top)
{
bufDC.DrawText(output, rOutput, DT_SINGLELINE | DT_CALCRECT);
if(lHeight == 0)
lHeight = rOutput.bottom;
}
tmp = "";
for(int i = 0; i < output.GetLength(); i++)
{
tmp += output.GetAt(i);
CRect rTmp(0,0,0,0);
bufDC.DrawText(tmp, rTmp, DT_SINGLELINE | DT_CALCRECT);
if(rTmp.right > rWnd.right)
{
CString hold;
hold = tmp.Right(1);
tmp = tmp.Left(tmp.GetLength()-1);
bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
rOutput.top += lHeight; rOutput.bottom += lHeight;
tmp = hold;
}
}
bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
}

dc.BitBlt(0, 0, rWnd.Width(), rWnd.Height(), &bufDC, 0, 0, SRCCOPY);

bufDC.SelectObject(pOldFont);
bufDC.SelectObject(pOldBmp);
}

void CCmdWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(m_hChildProcess != NULL)
{
CString tmp;
tmp.Format("%c", nChar);
WriteToChild(tmp);
}
else if(nChar >= 32)
{
CString tmp;
tmp.Format("%c", nChar);
m_curcommand += tmp;
Invalidate();
}
else if(nChar == VK_BACK)
{
m_curcommand = m_curcommand.Left(m_curcommand.GetLength()-1);
Invalidate();
}
else
CWnd::OnChar(nChar, nRepCnt, nFlags);
}

void CCmdWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(m_hChildProcess == NULL)
{
if(nChar == VK_RETURN)
{
CString output;
CHAR szPath[MAX_PATH]; //MAX_PATH is defined as 260
INT iLen = 0;
iLen = GetCurrentDirectory(MAX_PATH, szPath);
strcat(szPath, ">");

output = szPath;
output += m_curcommand;
if(m_output != "")
m_output += "\r\n";
m_output += output + "\r\n";

CString tmpout;
if(m_curcommand == "")
{
}
else if(ExecuteInternalFile(m_curcommand, tmpout))
{
AddCommand(m_curcommand);
if(tmpout != "")
m_output += tmpout;
}
else if(ExecuteExternal(m_curcommand))
{
AddCommand(m_curcommand);
if(tmpout != "")
m_output += tmpout;
}
else
{
AddCommand(m_curcommand);
m_output += m_curcommand + "' is not recognized as an internal or external command.";
}

m_curcommand = "";

Invalidate();
}
else if(nChar == VK_UP)
{
GetPrevCommand(m_curcommand);
Invalidate();
}
else if(nChar == VK_DOWN)
{
GetNextCommand(m_curcommand);
Invalidate();
}
}
else // The child process is running
{
if(nChar == VK_RETURN)
{
WriteToChild("\r\n");
}
}
}

BOOL CCmdWnd::ExecuteInternalFile(CString ExeName, CString &Response)
{
if(ExeName == "cls")
{
m_output = "";
return TRUE;
}
else if(ExeName == "exit")
{
GetParent()->PostMessage(WM_CLOSE);
return TRUE;
}

return FALSE;
}

int CCmdWnd::GetNewLineCount(CString text)
{
int icount = 0;
for(int i = 0; i < text.GetLength(); i++)
{
if(text.GetAt(i) == '\n')
icount++;
}
return icount;
}

BOOL CCmdWnd::ExecuteExternal(CString FileName)
{
HANDLE hOutputReadTmp, hOutputWrite;
HANDLE hInputWriteTmp, hInputRead;
HANDLE hErrorWrite;
HANDLE hThread;
DWORD ThreadId;
SECURITY_ATTRIBUTES sa;

if(FileName.GetLength() > 1023)
return FALSE;

char command[1024];
strcpy(command, FileName.GetBuffer(FileName.GetLength()));

m_hChildProcess = NULL;

// Set up the security attributes struct.
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

// Create the child output pipe.
if(!CreatePipe(&hOutputReadTmp, &hOutputWrite, &sa, 0))
{
TRACE0("CreatePipe Failed!\r\n");
}

// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
GetCurrentProcess(),&hErrorWrite,0,
TRUE,DUPLICATE_SAME_ACCESS))
{
TRACE0("DuplicateHandle Failed!\r\n");
}


// Create the child input pipe.
if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0))
{
}

if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&m_hPipeRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
{
}

if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
GetCurrentProcess(),
&m_hPipeWrite, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
{
}

if(!CloseHandle(hOutputReadTmp))
{
}
if(!CloseHandle(hInputWriteTmp))
{
}

PROCESS_INFORMATION pi;
STARTUPINFO si;

// Set up the start up info struct.
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hOutputWrite;
si.hStdInput = hInputRead;
si.hStdError = hErrorWrite;

if(!CreateProcess(NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
{
CString path;
char szSysPath[MAX_PATH];
GetEnvironmentVariable(_T("WinDir"), szSysPath, MAX_PATH);
path = szSysPath;
path += "\\system32\\";
path += command;
strcpy(command, path);
if(!CreateProcess(NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
{
return FALSE;
}
}

// Set global child process handle to cause threads to exit.
m_hChildProcess = pi.hProcess;

// Close any unnecessary handles.
if(!CloseHandle(pi.hThread))
{
}
if(!CloseHandle(hOutputWrite))
{
}
if(!CloseHandle(hInputRead))
{
}
if(!CloseHandle(hErrorWrite))
{
}


// Read the child's output.
AfxBeginThread(ReadAndHandleOutput, this);
// Redirection is complete

return TRUE;
}

UINT CCmdWnd::ReadAndHandleOutput(LPVOID pParam)
{
CCmdWnd *pWnd = (CCmdWnd*)pParam;
HANDLE hPipeRead = pWnd->m_hPipeRead;
CHAR *lpBuffer;
DWORD nBytesRead;
DWORD nCharsWritten;

while(TRUE)
{
lpBuffer = new CHAR[1024];
if(!ReadFile(hPipeRead, lpBuffer, 1020, &nBytesRead, NULL) || !nBytesRead)
{
if(GetLastError() == ERROR_BROKEN_PIPE)
{
delete [] lpBuffer;
break; // pipe done - normal exit path.
}
else
{
delete [] lpBuffer;
break;
// Something bad happened.
}
}

lpBuffer[nBytesRead] = '\0';
pWnd->PostMessage(UWM_CONSOLEOUTPUT, 0, (LPARAM)lpBuffer);
}

pWnd->SendMessage(UWM_CHILDEXIT);
return 0;
}

LRESULT CCmdWnd::OnConsoleOutput(WPARAM wParam, LPARAM lParam)
{
m_output += (CHAR*)lParam;

delete [] (CHAR*)lParam;

Invalidate();

return 0;
}

BOOL CCmdWnd::WriteToChild(CString text)
{
m_output += text;
char *lpszBuf = new char[text.GetLength()+1];
strcpy(lpszBuf, text);
DWORD nBytesWrite = strlen(lpszBuf);
DWORD nBytesWrote;
if(!WriteFile(m_hPipeWrite, lpszBuf, nBytesWrite, &nBytesWrote, NULL))
{
delete [] lpszBuf;
if(GetLastError() == ERROR_NO_DATA)
return FALSE; // Pipe was closed (normal exit path).
else
return FALSE;
}

delete [] lpszBuf;
Invalidate();
return TRUE;
}

LRESULT CCmdWnd::OnChildExit(WPARAM wParam, LPARAM lParam)
{
CloseHandle(m_hPipeRead);
CloseHandle(m_hPipeWrite);
CloseHandle(m_hChildProcess);
m_hChildProcess = NULL;

Invalidate();
return 0;
}

About December 2006

This page contains all entries posted to Photon Development in December 2006. They are listed from oldest to newest.

November 2006 is the previous archive.

February 2007 is the next archive.

Many more can be found on the main index page or by looking through the archives.