DT_DLL.CPP

/*++ 

Copyright (c) 1995 Intel Corp

File Name:

dt_dll.cpp

Abstract:

Contains main and supporting functions for a Debug/Trace
DLL for the WinSock2 DLL. See the design spec
for more information.

--*/

//
// Include Files
//

#include "nowarn.h" /* turn off benign warnings */
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */
#endif

#include "nowarn.h" /* some warnings may have been turned back on */
#include <winsock2.h>
#include <stdarg.h>
#include <ws2spi.h>
#include <commdlg.h>

#include "dt_dll.h"
#include "cstack.h"
#include "dt.h"
#include "handlers.h"

//
// Forward References for Functions
//

LRESULT APIENTRY
DTMainWndProc(
IN HWND WindowHandle,
IN UINT Message,
IN WPARAM WParam,
IN LPARAM LParam);

LRESULT APIENTRY
DTEditWndProc(
IN HWND WindowHandle,
IN UINT Message,
IN WPARAM WParam,
IN LPARAM LParam);

BOOL WINAPI
DllMain(
HINSTANCE DllInstHandle,
DWORD Reason,
LPVOID Reserved);

DWORD
WindowThreadFunc(LPDWORD TheParam);

BOOL APIENTRY
DebugDlgProc(
IN HWND hwndDlg,
IN UINT message,
IN WPARAM wParam,
IN LPARAM lParam);

BOOL
GetFile(
IN HWND OwnerWindow,
OUT LPSTR Buffer,
IN DWORD BufSize);

void
AbortAndClose(
IN HANDLE FileHandle,
IN HWND WindowHandle);




//
// Externally Visible Global Variables
//

HWND DebugWindow; // handle to the child edit control
HANDLE LogFileHandle; // handle to the log file
DWORD OutputStyle = WINDOW_ONLY; // where to put output
char Buffer[TEXT_LEN]; // buffer for building output strings



//
// Static Global Variables
//

// name for my window class
static char DTWndClass[] = "DTWindow";

static HWND FrameWindow; // handle to frame of debug window
static WNDPROC EditWndProc; // the edit control's window proc
static HINSTANCE DllInstHandle; // handle to the dll instance
static DWORD TlsIndex; // tls index for this module
static CRITICAL_SECTION CrSec; // critical section for text output
static HANDLE TextOutEvent; // set when debug window is ready

static char LogFileName[256]; // name of the log file

// handle to and id of the main thread of the DLL which initializes
// and creates windows, etc
static HANDLE WindowThread;
static DWORD WindowThreadId;

// function pointer tables for handler functions.
static LPFNDTHANDLER HdlFuncTable[MAX_DTCODE + 1];

// static strings
static char ErrStr1[] = "Couldn't open file. Debug output will go to \
debug window.";
static char ErrStr2[] = "An error occurred while trying to get a log \
filename. Debug output will go to the window only.";
static char ErrStr3[] = "Had problems writing to file. Aborting file \
ouput -- all debug output will now go to the debugging window";




//
// Function Definitions
//


BOOL WINAPI
DllMain(
HINSTANCE InstanceHandle,
DWORD Reason,
LPVOID Reserved)
/*++

DllMain()

Function Description:

Please see Windows documentation for DllEntryPoint.

Arguments:

Please see windows documentation.

Return Value:

Please see windows documentation.

--*/
{

Cstack_c *ThreadCstack; // points to Cstack objects in tls
PINITDATA InitDataPtr; // to pass to the window creation thread

switch(Reason) {

// Determine the reason for the call and act accordingly.

case DLL_PROCESS_ATTACH:

DllInstHandle = InstanceHandle;
InitializeCriticalSection(&CrSec);
TextOutEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

// Fill in the handler function table.
DTHandlerInit(HdlFuncTable, MAX_DTCODE);

// Allocate a TLS index.
TlsIndex = TlsAlloc();

// Pop up a dialog box for the user to choose output method.
DialogBox(DllInstHandle,
MAKEINTRESOURCE(IDD_DIALOG1),
NULL,
(DLGPROC)DebugDlgProc);

if ((OutputStyle == FILE_ONLY) || (OutputStyle == FILE_AND_WINDOW)) {

LogFileHandle = CreateFile(LogFileName,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (LogFileHandle == INVALID_HANDLE_VALUE) {
OutputStyle = WINDOW_ONLY;
MessageBox(NULL, ErrStr1, "Error", MB_OK | MB_ICONSTOP);
}
}

// Get some information for later output to the debug window
// or file -- get the time, PID, and TID of the calling
// process and put into a INITDATA struct. This memory will
// be freed by the thread it is passed to.
InitDataPtr = (PINITDATA) LocalAlloc(0, sizeof(INITDATA));
GetLocalTime(&(InitDataPtr->LocalTime));
InitDataPtr->TID = GetCurrentThreadId();
InitDataPtr->PID = GetCurrentProcessId();

// Create the initialization/window handling thread.
if ((OutputStyle == WINDOW_ONLY) || (OutputStyle == FILE_AND_WINDOW)) {
WindowThread =
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)WindowThreadFunc,
(LPVOID)InitDataPtr,
0,
&WindowThreadId);
} else {

// Normally the window thread does a DTTextOut of the time
// and process info that we saved just above. But in this
// case, there is no window thread so spit it out to the
// file.

wsprintf(Buffer, "Log initiated: %d-%d-%d, %d:%d:%d\r\n",
InitDataPtr->LocalTime.wMonth,
InitDataPtr->LocalTime.wDay,
InitDataPtr->LocalTime.wYear,
InitDataPtr->LocalTime.wHour,
InitDataPtr->LocalTime.wMinute,
InitDataPtr->LocalTime.wSecond);
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
wsprintf(Buffer, "Process ID: 0x%X Thread ID: 0x%X\r\n",
InitDataPtr->PID,
InitDataPtr->TID);
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);

// Setting this event allows {Pre|Post}ApiNotify to
// proceed. This event isn't really needed in this case
// (because there is only one thread, and we know the code
// above has been executed before WSAPre|PostApiNotify).
SetEvent(TextOutEvent);
}

// flow through...

case DLL_THREAD_ATTACH:

// Store a pointer to a new Cstack_c in the slot for this
// thread.
ThreadCstack = new Cstack_c();
TlsSetValue(TlsIndex, (LPVOID)ThreadCstack);

break;

case DLL_PROCESS_DETACH:

// Free up some resources. This is like cleaning up your room
// before the tornado strikes, but hey, it's good practice.
TlsFree(TlsIndex);
DeleteCriticalSection(&CrSec);

if ((OutputStyle == FILE_ONLY) || (OutputStyle == FILE_AND_WINDOW)) {
CloseHandle(LogFileHandle);
}
CloseHandle(WindowThread);

break;

case DLL_THREAD_DETACH:

// Get the pointer to this thread's Cstack, and delete the
// object.
ThreadCstack = (Cstack_c *)TlsGetValue(TlsIndex);
delete ThreadCstack;

break;

default:

break;
} // switch (Reason)

return TRUE;
} // DllMain()





BOOL WINAPIV
WSAPreApiNotify(
IN INT NotificationCode,
OUT LPVOID ReturnCode,
IN LPSTR LibraryName,
...)
/*++

Function Description:

Builds a string for output and passes it, along with information
about the call, to a handler function.

Arguments:

NotificationCode -- specifies which API function called us.

ReturnCode -- a generic pointer to the return value of the API
function. Can be used to change the return value in the
case of a short-circuit (see how the return value from
PreApiNotify works for more information on short-circuiting
the API function).

LibraryName -- a string pointing to the name of the library that
called us.

... -- variable number argument list. These are pointers
to the actual parameters of the API functions.

Return Value:

Returns TRUE if we want to short-circuit the API function;
in other words, returning non-zero here forces the API function
to return immediately before any other actions take place.

Returns FALSE if we want to proceed with the API function.

--*/
{
va_list vl; // used for variable arg-list parsing
Cstack_c *ThreadCstack; // the Cstack_c object for this thread
int Index = 0; // index into string we are creating
BOOL ReturnValue; // value to return
LPFNDTHANDLER HdlFunc; // pointer to handler function
int Counter; // counter popped off the cstack
int OriginalError; // any pending error is saved
int HandlerError; // the error after handler returns

EnterCriticalSection(&CrSec);
OriginalError = GetLastError();

// Wait until the debug window is ready to receive text for output.
WaitForSingleObject(TextOutEvent, INFINITE);
va_start(vl, LibraryName);

// Get the Cstack_c object for this thread.
ThreadCstack = (Cstack_c *)TlsGetValue(TlsIndex);
if (!ThreadCstack){
ThreadCstack = new Cstack_c();
TlsSetValue(TlsIndex, (LPVOID)ThreadCstack);
wsprintf(Buffer, "0x%X Foriegn thread\n",
GetCurrentThreadId());
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
} //if

// Start building an output string with some info that's
// independent of which API function called us.
Index += wsprintf(Buffer, "TID: 0x%X ", GetCurrentThreadId());
Index += wsprintf(Buffer + Index, "Function call: %d ",
ThreadCstack->CGetCounter());

// Push the counter & increment.
ThreadCstack->CPush();

// Reset the error to what it was when the function started.
SetLastError(OriginalError);

// Call the appropriate handling function, output the buffer.
if ((NotificationCode < MAX_DTCODE) && HdlFuncTable[NotificationCode]) {
HdlFunc = HdlFuncTable[NotificationCode];
ReturnValue = (*HdlFunc)(vl, ReturnCode,
LibraryName,
Buffer,
Index,
TEXT_LEN,
TRUE);
HandlerError = GetLastError();

} else {

wsprintf(Buffer + Index, "Unknown function called!\r\n");
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
ReturnValue = FALSE;
}

// If we are returning TRUE, then the API/SPI function will be
// short-circuited. We must pop the thread stack, since no
// corresponding WSAPostApiNotify will be called.
if (ReturnValue) {
ThreadCstack->CPop(Counter);
}

// In case the error has changed since the handler returned, we
// want to set it back to that. So if the handler set the error,
// the function exits with that value; if not, it exits with the
// original error.
SetLastError(HandlerError);
LeaveCriticalSection(&CrSec);
return(ReturnValue);

} // WSAPreApiNotify()






BOOL WINAPIV
WSAPostApiNotify(
IN INT NotificationCode,
OUT LPVOID ReturnCode,
IN LPSTR LibraryName,
...)
/*++

PostApiNotify()

Function Description:

Like PreApiNotify, builds a string and passes it, along with
information about the call, to a handler function.

Arguments:

NotificationCode -- specifies which API function called us.

ReturnCode -- a generic pointer to the return value of the API
function.

... -- variable number argument list. These are pointers
to the actual parameters of the API functions.

Return Value:

Returns value is currently meaningless.

--*/
{
va_list vl; // used for variable arg-list parsing
Cstack_c *ThreadCstack; // the Cstack_c object for this thread
int Index = 0; // index into string we are creating
int Counter; // counter we pop off the cstack
LPFNDTHANDLER HdlFunc; // pointer to handler function
int OriginalError; // any pending error is saved
int HandlerError; // error after the handler returns

// Lets hope EnterCriticalSection() doesn't change the error...
EnterCriticalSection(&CrSec);
OriginalError = GetLastError();

// Wait until it's ok to send output.
WaitForSingleObject(TextOutEvent, INFINITE);

va_start(vl, LibraryName);

// Get the cstack object from TLS, pop the Counter.
ThreadCstack = (Cstack_c *) TlsGetValue(TlsIndex);

if (!ThreadCstack){
ThreadCstack = new Cstack_c();
TlsSetValue(TlsIndex, (LPVOID)ThreadCstack);
wsprintf(Buffer, "0x%X Foriegn thread\n",
GetCurrentThreadId());
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
} //if

ThreadCstack->CPop(Counter);

// Output some info that's independent of which API called us.
Index += wsprintf(Buffer, "TID: 0x%X ", GetCurrentThreadId());
Index += wsprintf(Buffer + Index, "Function Call: %d ", Counter);

// Set the error to what it originally was.
SetLastError(OriginalError);

// Call the appropriate handling function, output the buffer.
if ((NotificationCode < MAX_DTCODE) && HdlFuncTable[NotificationCode]) {
HdlFunc = HdlFuncTable[NotificationCode];
(*HdlFunc)(vl, ReturnCode,
LibraryName,
Buffer,
Index,
TEXT_LEN,
FALSE);
HandlerError = GetLastError();

} else {

wsprintf(Buffer + Index, "Unknown function returned!\r\n");
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
}

// In case the error has changed since the handler returned, we
// want to set it back to that. So if the handler set the error,
// the function exits with that value; if not, it exits with the
// original error.
SetLastError(HandlerError);
LeaveCriticalSection(&CrSec);
return(FALSE);

} // WSAPostApiNotify()





LRESULT APIENTRY
DTMainWndProc(
IN HWND WindowHandle,
IN UINT Message,
IN WPARAM WParam,
IN LPARAM LParam)
/*++

DTMainWndProc()

Function Description:

Window procedure for the main window of the Dll. This function
processes WM_CREATE messages in order to create a child
edit control, which does most of the dirty work. Also processes
WM_COMMAND to trap notification messages from the edit control,
as well as WM_SIZE and WM_DESTROY messages.

Arguments:

WindowHandle -- the window.

Message -- the message.

WParam -- first parameter.

LParam -- second parameter.

Return Value:

Message dependent.

--*/
{

HFONT FixedFontHandle; // self-explanatory
RECT Rect; // specifies client area of frame window
DWORD CharIndex1;
DWORD CharIndex2;
DWORD LineIndex; // indices into edit control text
char NullString[] = ""; // self-explanatory
DWORD OldOutputStyle; // temporary storage for OutputStyle

switch (Message) {

case WM_CREATE:

// Create the debug window as a multiline edit control.
GetClientRect(WindowHandle, &Rect);
DebugWindow = CreateWindow("EDIT",
NULL,
WS_CHILD | WS_VISIBLE |
WS_VSCROLL | ES_LEFT |
ES_MULTILINE | ES_AUTOVSCROLL,
0,
0,
Rect.right,
Rect.bottom,
WindowHandle,
(HMENU)EC_CHILD,
DllInstHandle,
NULL);

// Subclass the edit control's window procedure to be
// DTEditWndProc.
EditWndProc = (WNDPROC) SetWindowLong(DebugWindow,
GWL_WNDPROC,
(DWORD)DTEditWndProc);

// Set the edit control's text size to the maximum.
SendMessage(DebugWindow, EM_LIMITTEXT, 0, 0);

// Set the edit control's font
FixedFontHandle = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(DebugWindow, WM_SETFONT, (WPARAM)FixedFontHandle,
MAKELPARAM(TRUE, 0));

return(0);

case WM_COMMAND:

if (LOWORD(WParam) == EC_CHILD) {

// The notification is coming from the edit-control child.
// Determine which notification it is and act appropriately.

switch (HIWORD(WParam)) {

case EN_ERRSPACE:

// Flow through

case EN_MAXTEXT:

// There's too much text in the edit control. This is
// a hack to eliminate approximately the first half of
// the text, so we can then add more...
CharIndex1 = GetWindowTextLength(DebugWindow) / 2;
LineIndex = SendMessage(DebugWindow, EM_LINEFROMCHAR,
(WPARAM)CharIndex1, 0);
CharIndex2 = SendMessage(DebugWindow, EM_LINEINDEX,
(WPARAM)LineIndex, 0);

SendMessage(DebugWindow, EM_SETSEL, 0, CharIndex2);
SendMessage(DebugWindow, EM_REPLACESEL, 0,
(LPARAM)NullString);

// send this text to the window only...
OldOutputStyle = OutputStyle;
OutputStyle = WINDOW_ONLY;
DTTextOut(DebugWindow, LogFileHandle,
"----Buffer Overflow...Resetting----\r\n",
OutputStyle);
OutputStyle = OldOutputStyle;
break;

case EN_CHANGE:
case EN_UPDATE:

// Ignore these notification codes
return 0;
break;

default:

// Let the default window procedure handle it.
return DefWindowProc(WindowHandle, Message, WParam,
LParam);
} // switch (HIWORD(WParam))

} // if (LOWORD(WParam) == EC_CHILD)
else {

// The notification is coming from somewhere else!!!
return DefWindowProc(WindowHandle, Message, WParam,
LParam);
}

return(0);
break;

case WM_DESTROY:

PostQuitMessage(0);
return(0);

case WM_SIZE:


// Make the edit control the size of the window's client area.
MoveWindow(DebugWindow, 0, 0, LOWORD(LParam), HIWORD(LParam), TRUE);
return(0);

default:

// All other messages are taken care of by the default.
return(DefWindowProc(WindowHandle, Message, WParam, LParam));

} // switch

} // DTMainWndProc()





LRESULT APIENTRY
DTEditWndProc(
IN HWND WindowHandle,
IN UINT Message,
IN WPARAM WParam,
IN LPARAM LParam)
/*++

DTEditWndProc()

Function Description:

Subclassed window procedure for the debug window. This function
disables some edit control functionality, and also responds to a
user-defined message to print out text in the window.

Arguments:

WindowHandle -- the window.

Message -- the message.

WParam -- first parameter.

LParam -- second parameter.

Return Value:

Message dependent.

--*/
{
switch (Message) {

case WM_CHAR:

// Handle control-c so that copy works. Sorry about the magic
// number!
if (WParam == 3) {
return (CallWindowProc((WNDPROC)EditWndProc, WindowHandle, Message,
WParam, LParam));
} // else flows through

case WM_KEYDOWN: // Flow through
case WM_UNDO: // Flow through
case WM_PASTE: // Flow through
case WM_CUT:

return (0); // Effectively disables the above messages

default:

return (CallWindowProc(EditWndProc, WindowHandle, Message,
WParam, LParam));
} // switch

} // DTEditWndProc()





DWORD
WindowThreadFunc(
LPDWORD TheParam)
/*++

WindowThreadFunc()

Function Description:

Thread function for WindowThread created in DllMain during
process attachment. Registers a window class, creates an
instance of that class, and goes into a message loop to retrieve
messages for that window or it's child edit control.

Arguments:

TheParam -- Pointer to the parameter passed in by the function
that called CreateThread.

Return Value:

Returns the wParam of the quit message that forced us out of the
message loop.

--*/
{

WNDCLASS wnd_class; // window class structure to register
MSG msg; // retrieved message
PINITDATA InitDataPtr; // casts TheParam into a INITDATA pointer

// Register a window class for the frame window.
wnd_class.style = CS_HREDRAW | CS_VREDRAW;
wnd_class.lpfnWndProc = DTMainWndProc;
wnd_class.cbClsExtra = 0;
wnd_class.cbWndExtra = 0;
wnd_class.hInstance = DllInstHandle;
wnd_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wnd_class.hCursor = LoadCursor(NULL, IDC_ARROW);
wnd_class.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wnd_class.lpszMenuName = NULL;
wnd_class.lpszClassName = DTWndClass;
RegisterClass(&wnd_class);

// Create a frame window
FrameWindow = CreateWindow(DTWndClass,
"Debug Window",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
DllInstHandle,
NULL);

// Send the initialization data to the debug window and/or file.
InitDataPtr = (PINITDATA)TheParam;
wsprintf(Buffer, "Log initiated: %d-%d-%d, %d:%d:%d\r\n",
InitDataPtr->LocalTime.wMonth,
InitDataPtr->LocalTime.wDay,
InitDataPtr->LocalTime.wYear,
InitDataPtr->LocalTime.wHour,
InitDataPtr->LocalTime.wMinute,
InitDataPtr->LocalTime.wSecond);
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
wsprintf(Buffer, "Process ID: 0x%X Thread ID: 0x%X\r\n",
InitDataPtr->PID,
InitDataPtr->TID);
DTTextOut(DebugWindow, LogFileHandle, Buffer, OutputStyle);
LocalFree(InitDataPtr);

// Setting this event allows {Pre|Post}ApiNotify to proceed. This
// insures (ensures? what's the difference) that any debugging
// output by other threads is held up until after this statement.
SetEvent(TextOutEvent);

// Go into a message loop.
while (GetMessage(&msg, NULL, 0 , 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return(msg.wParam);

} // WindowThreadFunc()





BOOL APIENTRY
DebugDlgProc(
HWND DialogWindow,
UINT Message,
WPARAM WParam,
LPARAM LParam)
/*++

DebugDlgProc()

Function Description:

Window function for the dialog box IDC_DIALOG1, the dialog box
that pops up when the dll is loaded and prompts the user for the
output style of his/her choice.

Arguments:

DialogWindow -- handle to the dialog box window.

Message -- the message being received.

WParam -- first parameter.

LParam -- second parameter.

Return Value:

Returns TRUE to indicate message was handled, FALSE otherwise.

--*/
{

DWORD LogFNSize = sizeof(LogFileName); // size of the file name buffer

switch (Message) {

case WM_COMMAND:

switch (LOWORD(WParam)) {

case IDOK:

// The user clicked the OK button...figure out his choice
// and act appropriately.
if (IsDlgButtonChecked(DialogWindow, IDC_RADIO5)) {

// Radio Button 1 was clicked.
if (!GetFile(DialogWindow, LogFileName, LogFNSize)) {

// Error -- OutputStyle stays WINDOW_ONLY.
MessageBox(DialogWindow, ErrStr2, "Error.",
MB_OK | MB_ICONSTOP);
} else {
OutputStyle = FILE_ONLY;
}

} else if (IsDlgButtonChecked(DialogWindow, IDC_RADIO6)) {

// Radio Button 2 was clicked.
OutputStyle = WINDOW_ONLY;

} else if (IsDlgButtonChecked(DialogWindow, IDC_RADIO7)) {

// Radio Button 3 was clicked.
if (!GetFile(DialogWindow, LogFileName, LogFNSize)) {

// Error -- OutputStyle stays WINDOW_ONLY.
MessageBox(DialogWindow, ErrStr2, "Error",
MB_OK | MB_ICONSTOP);
} else {
OutputStyle = FILE_AND_WINDOW;
}

} else if (IsDlgButtonChecked(DialogWindow, IDC_RADIO8)) {

// Radio Button 4 was clicked.
OutputStyle = DEBUGGER;

} else {

// No radio buttons were clicked -- pop up a Message
// box.
MessageBox(DialogWindow, "You must choose one output method.",
"Choose or Die.", MB_OK | MB_ICONSTOP);
break;

}

// flow through

case IDCANCEL:

EndDialog(DialogWindow, WParam);
return TRUE;

}

case WM_INITDIALOG:

return TRUE;

}
return FALSE;

} // DebugDlgProc()





BOOL
DTTextOut(
IN HWND WindowHandle,
IN HANDLE FileHandle,
IN char *String,
DWORD Style)
/*++

DTTextOut()

Function Description:

This function outputs a string to a debug window and/or file.

Arguments:

WindowHandle -- handle to an edit control for debug output.

FileHandle -- handle to an open file for debug output.

String -- the string to output.

Style -- specifies whether the output should go to the window,
the file, or both.

Return Value:

Returns TRUE if the output succeeds, FALSE otherwise.

--*/
{

DWORD NumWritten; // WriteFile takes an address to this
DWORD Index; // index of end of edit control text
BOOL Result; // result of WriteFile
char Output[TEXT_LEN]; // scratch buffer

static DWORD LineCount = 0; // text output line number
DWORD BufIndex = 0; // index into output string

// Build a new string with the line-number in front.

BufIndex += wsprintf(Output, "(%d) ", LineCount++); 
strcpy(Output + BufIndex, String);

switch (Style) {

case WINDOW_ONLY:

Index = GetWindowTextLength(WindowHandle);
SendMessage(WindowHandle, EM_SETSEL, Index, Index);
SendMessage(WindowHandle, EM_REPLACESEL, 0, (LPARAM)Output);

break;

case FILE_ONLY:

Result = WriteFile(FileHandle, (LPCVOID)Output, strlen(Output),
&NumWritten, NULL);
if (!Result) {

AbortAndClose(FileHandle, WindowHandle);
return FALSE;
}
break;

case FILE_AND_WINDOW:

Index = GetWindowTextLength(WindowHandle);
SendMessage(WindowHandle, EM_SETSEL, Index, Index);
SendMessage(WindowHandle, EM_REPLACESEL, 0, (LPARAM)Output);
Result = WriteFile(FileHandle, (LPCVOID)Output, strlen(Output),
&NumWritten, NULL);
if (!Result) {

AbortAndClose(FileHandle, WindowHandle);
return FALSE;
}
break;

case DEBUGGER:

OutputDebugString(Output);
}
return TRUE;

} // DTTextOut()





void
AbortAndClose(
IN HANDLE FileHandle,
IN HWND WindowHandle)
/*++

AbortAndClose()

Function Description:

Closes a file handle, informs the user via a message box, and
changes the global variable OutputStyle to WINDOW_ONLY

Arguments:

FileHandle -- handle to a file that caused the error.

WindowHandle -- handle to a window to be the parent of the
Message Box.

Return Value:

Void.

--*/
{
CloseHandle(FileHandle);
MessageBox(WindowHandle, ErrStr3, "Error", MB_OK | MB_ICONSTOP);
OutputStyle = WINDOW_ONLY;

} // AbortAndClose()





BOOL
GetFile(
IN HWND OwnerWindow,
OUT LPSTR FileName,
IN DWORD FileNameSize)
/*++

GetFile()

Function Description:

Uses the predefined "Save As" dialog box style to retrieve a
file name from the user. The file name the user selects is
stored in LogFileName.

Arguments:

OwnerWindow -- window which will own the dialog box.

FileName -- address of a buffer in which to store the string.

FileNameSize -- size of the FileName buffer.

Return Value:

Returns whatever GetSaveFileName returns; see documentation for
that function.

--*/
{

OPENFILENAME OpenFileName; // common dialog box structure
char DirName[256]; // directory string
char FileTitle[256]; // file-title string

FileName[0] = '\0';

FillMemory((PVOID)&OpenFileName, sizeof(OPENFILENAME), 0);

// Retrieve the system directory name and store it in DirName.
GetCurrentDirectory(sizeof(DirName), DirName);

// Set the members of the OPENFILENAME structure.
OpenFileName.lStructSize = sizeof(OPENFILENAME);
OpenFileName.hwndOwner = OwnerWindow;
OpenFileName.lpstrFilter = OpenFileName.lpstrCustomFilter = NULL;
OpenFileName.nFilterIndex = 0;
OpenFileName.lpstrFile = FileName;
OpenFileName.nMaxFile = FileNameSize;
OpenFileName.lpstrFileTitle = FileTitle;
OpenFileName.nMaxFileTitle = sizeof(FileTitle);
OpenFileName.lpstrInitialDir = DirName;
OpenFileName.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;

// Pop up the dialog box to get the file name.
return GetSaveFileName(&OpenFileName);

} // GetFile()