/*++ 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. Author: Michael A. Grafton --*/ // // Include Files // #include "nowarn.h" /* turn off benign warnings */ #ifndef _WINSOCKAPI_ #define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ #endif #include #include "nowarn.h" /* some warnings may have been turned back on */ #include #include #include #include #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(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()