Sending console commands via stdin (C#)
by Peter Simard · in Torque Game Engine · 02/19/2008 (6:23 pm) · 7 replies
I am currently developing a TGE server manager in C# and am experiencing a problem. I have successfully started the zones, and can read the console output via stdout fine. When I attempt to send back commands to the servers via stdin I get no response. I am relatively sure I have the process defined correctly as stdout works fine. Here is the code I am using:
Judging from the console c++ files, \r is the correct line terminator. I have also tried other combinations. No matter what I send via stdin I see no results from the console. So am I missing something obvious?
StreamWriter npin = selectedServer.process.StandardInput;
npin.WriteLine(textBox1.Text + "\r");Judging from the console c++ files, \r is the correct line terminator. I have also tried other combinations. No matter what I send via stdin I see no results from the console. So am I missing something obvious?
#2
02/21/2008 (5:08 am)
I need to issue commands from a separate process. I am not entering the commands directly into TGE. So the problem is getting the text to TGE for it to do the eval on.
#3
02/21/2008 (9:43 am)
I'm not sure I fully understand. Are you connected to the TCP/IP administration console on the TGE server and trying to use stdin to output back over this connection?
#4
02/21/2008 (10:15 am)
I understand that, but why don't you just send a string to TGE (from your Server Manager, I suppose you're connected via a socket somehow?) and let TGE do the evaluate.
#5
I added these two functions:
and changed WinConsole::process:
I also added:
which is called from WinMain.
02/21/2008 (10:19 am)
I had to hack support for this into Windows dedicated server. We use it for testing.I added these two functions:
static bool isRedirected(HANDLE stdHandle)
{
DWORD type = GetFileType(stdHandle);
if (type == FILE_TYPE_DISK || type == FILE_TYPE_PIPE)
return true;
return false;
}
void redirectStdio()
{
FILE* fp;
HANDLE stdHandle;
int conHandle;
CONSOLE_SCREEN_BUFFER_INFO coninfo;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
coninfo.dwSize.Y = 2048;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
conHandle = _open_osfhandle((long)stdHandle, _O_TEXT);
if (conHandle != -1)
{
fp = _fdopen(conHandle, "w");
*stdout = *fp;
setvbuf(stdout, NULL, _IONBF, 0);
}
stdHandle = GetStdHandle(STD_INPUT_HANDLE);
conHandle = _open_osfhandle((long)stdHandle, _O_TEXT);
if (conHandle != -1)
{
fp = _fdopen(conHandle, "r");
*stdin = *fp;
setvbuf(stdin, NULL, _IONBF, 0);
}
stdHandle = GetStdHandle(STD_ERROR_HANDLE);
conHandle = _open_osfhandle((long)stdHandle, _O_TEXT);
if (conHandle != -1)
{
fp = _fdopen(conHandle, "w");
*stderr = *fp;
setvbuf(stderr, NULL, _IONBF, 0);
}
}and changed WinConsole::process:
void WinConsole::process()
{
if (!WindowsConsole)
return;
char outbuf[512];
S32 outpos = 0;
INPUT_RECORD rec[20];
DWORD i;
DWORD numEvents = 0;
DWORD availBytes = 0;
if (gStdInRedir)
{
DWORD type = GetFileType(stdIn);
if (type == FILE_TYPE_PIPE)
{
if (!PeekNamedPipe(stdIn, NULL, 0, NULL, &availBytes, NULL))
return;
}
if (type == FILE_TYPE_DISK || availBytes > 0)
{
if (!ReadFile(stdIn, stdInBuf, 512, &numEvents, NULL))
printf("error reading stdIn: %d\n", GetLastError());
else
{
bool newLine = false;
int i = 0;
while (i < numEvents)
{
if (stdInBuf[i] == '\r' || stdInBuf[i] == '\n')
newLine = true;
WindowsConsole->processKey(outbuf, outpos, stdInBuf[i]);
if (newLine)
{
while ((i < numEvents) && (stdInBuf[i] == '\r' || stdInBuf[i] == '\n'))
i++;
newLine = false;
}
else
i++;
}
}
}
}
// if "PeekConsoleInput" fails, it probably means that stdIn is not a console
else if (winConsoleEnabled && PeekConsoleInput(stdIn, rec, 20, &numEvents))
{
if (numEvents)
{
ReadConsoleInput(stdIn, rec, 20, &numEvents);
for (i = 0; i < numEvents; i++)
{
if (rec[i].EventType == KEY_EVENT)
{
KEY_EVENT_RECORD* ke = &(rec[i].Event.KeyEvent);
if (ke->bKeyDown)
{
if (ke->uChar.AsciiChar == 0)
WindowsConsole->processVirtualKey(outbuf, outpos, ke);
else
WindowsConsole->processKey(outbuf, outpos, ke);
}
}
}
}
}
if (outpos)
{
outbuf[outpos] = 0;
printf("%s", outbuf);
}
}I also added:
static void initConsole(bool allocConsole)
{
// this has to happen straight off, otherwise the handles
// get invalidated by the other init code
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
gStdInRedir = isRedirected(stdIn);
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
gStdOutRedir = isRedirected(stdOut);
gInteractive = !gStdInRedir && !gStdOutRedir;
if (allocConsole)
AllocConsole();
else if (!gStdInRedir || !gStdOutRedir)
{
AttachConProc func =(AttachConProc)GetProcAddress(GetModuleHandle("kernel32.dll"), "AttachConsole");
if (func != NULL)
func(ATTACH_PARENT_PROCESS);
}
if (gStdInRedir || !allocConsole)
SetStdHandle(STD_INPUT_HANDLE, stdIn);
if (gStdOutRedir || !allocConsole)
SetStdHandle(STD_OUTPUT_HANDLE, stdOut);
redirectStdio();
}which is called from WinMain.
#6
Are those custom functions you added to WindowsConsole? The only other error is with:
It seems AttachConProc is undefined as well as ATTACH_PARENT_PROCESS. Looking on MSDN ATTACH_PARENT_PROCESS should be defined in Wincon.h, but including it doesn't fix it.
Thanks for all the assistance.
02/21/2008 (10:48 am)
Thank you Tim! I appreciate you posting your code here. I am getting lots of compile errors when I drop in the code. I was able to fix most of them by including the proper headers and defining some global variables. However it seems there are two functions missing:WindowsConsole->processVirtualKey(outbuf, outpos, ke);
WindowsConsole->processKey(outbuf, outpos, ke);Are those custom functions you added to WindowsConsole? The only other error is with:
AttachConProc func =(AttachConProc)GetProcAddress(GetModuleHandle("kernel32.dll"), "AttachConsole");
if (func != NULL)
func(ATTACH_PARENT_PROCESS);It seems AttachConProc is undefined as well as ATTACH_PARENT_PROCESS. Looking on MSDN ATTACH_PARENT_PROCESS should be defined in Wincon.h, but including it doesn't fix it.
Thanks for all the assistance.
#7
I was pretty much providing it as a starting point...
Here's AttachConProc:
Here's the other two WindowsConsole functions... I may have added these or, more likely, refactored from existing code:
BTW, "AttachConsole" is Windows XP and Windows Vista, only.
From MSDN:
http://msdn2.microsoft.com/en-us/library/ms681952.aspx
So, not only do you need to include wincon.h, you probably need to define _WIN32_WINNT to 0x0501.
Hope that helps more.
02/21/2008 (11:37 am)
Our code is pretty changed from stock Torque 1.3.I was pretty much providing it as a starting point...
Here's AttachConProc:
typedef BOOL (WINAPI *AttachConProc)(DWORD);
Here's the other two WindowsConsole functions... I may have added these or, more likely, refactored from existing code:
void WinConsole::processKey(char* outbuf, S32& outpos, char c, bool shiftPressed)
{
switch (c)
{
case '\b':
{
if(inpos > 0)
{
outbuf[outpos++] = '\b';
outbuf[outpos++] = ' ';
outbuf[outpos++] = '\b';
inpos--;
}
}
break;
case '\t':
{
// In the output buffer, we're going to have to erase the current line (in case
// we're cycling through various completions) and write out the whole input
// buffer, so (inpos * 3) + complen <= 512. Should be OK. The input buffer is
// also 512 chars long so that constraint will also be fine for the input buffer.
{
// Erase the current line.
U32 i;
for (i = 0; i < inpos; i++)
{
outbuf[outpos++] = '\b';
outbuf[outpos++] = ' ';
outbuf[outpos++] = '\b';
}
// Modify the input buffer with the completion.
U32 maxlen = 512 - (inpos * 3);
inpos = Con::tabComplete(inbuf, inpos, maxlen, !shiftPressed);
// Copy the input buffer to the output.
for (i = 0; i < inpos; i++)
{
outbuf[outpos++] = inbuf[i];
}
}
}
break;
case '\n':
case '\r':
{
outbuf[outpos++] = '\r';
outbuf[outpos++] = '\n';
inbuf[inpos] = 0;
outbuf[outpos] = 0;
if (interactive)
printf("%s", outbuf);
S32 eventSize;
eventSize = ConsoleEventHeaderSize;
dStrcpy(postEvent.data, inbuf);
postEvent.size = eventSize + dStrlen(inbuf) + 1;
GameInterface::getInterface()->postEvent(postEvent);
// If we've gone off the end of our array, wrap
// back to the beginning
if (iCmdIndex >= MAX_CMDS)
iCmdIndex %= MAX_CMDS;
// Put the new command into the array
strcpy(rgCmds[iCmdIndex ++], inbuf);
if (interactive)
printf("%s", Con::getVariable("Con::Prompt"));
inpos = outpos = 0;
}
break;
default:
{
inbuf[inpos++] = c;
outbuf[outpos++] = c;
}
break;
}
}
void WinConsole::processVirtualKey(char* outbuf, S32& outpos, KEY_EVENT_RECORD* ke)
{
switch (ke->wVirtualKeyCode)
{
// UP ARROW
case 0x26 :
{
// Go to the previous command in the cyclic array
if ((-- iCmdIndex) < 0)
iCmdIndex = MAX_CMDS - 1;
// If this command isn't empty ...
if (rgCmds[iCmdIndex][0] != '[[60c1cad9aee7d]]')
{
// Obliterate current displayed text
for (S32 i = outpos = 0; i < inpos; i ++)
{
outbuf[outpos++] = '\b';
outbuf[outpos++] = ' ';
outbuf[outpos++] = '\b';
}
// Copy command into command and display buffers
for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos ++, outpos ++)
{
outbuf[outpos] = rgCmds[iCmdIndex][inpos];
inbuf[inpos] = rgCmds[iCmdIndex][inpos];
}
}
// If previous is empty, stay on current command
else if ((++ iCmdIndex) >= MAX_CMDS)
{
iCmdIndex = 0;
}
}
break;
// DOWN ARROW
case 0x28:
{
// Go to the next command in the command array, if
// it isn't empty
if (rgCmds[iCmdIndex][0] != '[[60c1cad9aee7d]]' && (++ iCmdIndex) >= MAX_CMDS)
iCmdIndex = 0;
// Obliterate current displayed text
for (S32 i = outpos = 0; i < inpos; i ++)
{
outbuf[outpos ++] = '\b';
outbuf[outpos ++] = ' ';
outbuf[outpos ++] = '\b';
}
// Copy command into command and display buffers
for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos ++, outpos ++)
{
outbuf[outpos] = rgCmds[iCmdIndex][inpos];
inbuf[inpos ] = rgCmds[iCmdIndex][inpos];
}
}
break;
// LEFT ARROW
case 0x25:
break;
// RIGHT ARROW
case 0x27:
break;
default:
break;
}
}BTW, "AttachConsole" is Windows XP and Windows Vista, only.
From MSDN:
Quote:
To compile an application that uses this function, define _WIN32_WINNT as 0x0501 or later. For more information, see Using the SDK Headers.
http://msdn2.microsoft.com/en-us/library/ms681952.aspx
So, not only do you need to include wincon.h, you probably need to define _WIN32_WINNT to 0x0501.
Hope that helps more.
Torque Owner Stefan Lundmark