Win9xConHook.c revision 26a4456dd6f1a5d7d7fff766551461a578687c4a
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2004 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
#ifdef WIN32
/*
* Win9xConHook.dll - a hook proc to clean up Win95/98 console behavior.
*
* It is well(?) documented by Microsoft that the Win9x HandlerRoutine
* hooked by the SetConsoleCtrlHandler never receives the CTRL_CLOSE_EVENT,
* CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT signals.
*
* It is possible to have a second window to monitor the WM_ENDSESSION
* message, but the close button still fails..
*
* There is a 16bit polling method for the close window option, but this
* is CPU intensive and requires thunking.
*
* Attempts to subclass the 'tty' console fail, since that message thread
* is actually owned by the 16 bit winoldap.mod process, although the
* window reports it is owned by the process/thread of the console app.
*
* Win9xConHook is thunks the WM_CLOSE and WM_ENDSESSION messages,
* first through a window hook procedure in the winoldap context, into
* a subclass WndProc, and on to a second hidden monitor window in the
* console application's context that dispatches them to the console app's
* registered HandlerRoutine.
*/
/* This debugging define turns on output to COM1, although you better init
* the port first (even using hyperterm). It's the only way to catch the
* goings on within system logoff/shutdown.
* #define DBG 1
*/
#include <windows.h>
/* Variables used within any process context:
* hookwndmsg is a shared message to send Win9xConHook signals
* origwndprop is a wndprop atom to store the orig wndproc of the tty
* hookwndprop is a wndprop atom to store the hwnd of the hidden child
* is_service reminds us to unmark this process on the way out
*/
static UINT hookwndmsg = 0;
static LPCTSTR origwndprop;
static LPCTSTR hookwndprop;
static BOOL is_service = 0;
//static HMODULE hmodThis = NULL;
/* Variables used within the tty processes' context:
* is_tty flags this process; -1 == unknown, 1 == if tty, 0 == if not
* hw_tty is the handle of the top level tty in this process context
* is_subclassed is toggled to assure DllMain removes the subclass on unload
* hmodLock is there to try and prevent this dll from being unloaded if the
* hook is removed while we are subclassed
*/
static int is_tty = -1;
static HWND hwtty = NULL;
static BOOL is_subclassed = 0;
// This simply causes a gpfault the moment it tries to FreeLibrary within
// the subclass procedure ... not good.
//static HMODULE hmodLock = NULL;
/* Variables used within the service or console app's context:
* hmodHook is the instance handle of this module for registering the hooks
* hhkGetMessage is the hook handle for catching Posted messages
* hhkGetMessage is the hook handle for catching Sent messages
* monitor_hwnd is the invisible window that handles our tty messages
* the tty_info strucure is used to pass args into the hidden window's thread
*/
static HMODULE hmodHook = NULL;
static HHOOK hhkGetMessage;
//static HHOOK hhkCallWndProc;
static HWND monitor_hwnd = NULL;
typedef struct {
PHANDLER_ROUTINE phandler;
HINSTANCE instance;
HWND parent;
INT type;
LPCSTR name;
} tty_info;
/* These are the GetWindowLong offsets for the hidden window's internal info
* gwltty_phandler is the address of the app's HandlerRoutine
* gwltty_ttywnd is the tty this hidden window will handle messages from
*/
#define gwltty_phandler 0
#define gwltty_ttywnd 4
/* Forward declaration prototypes for internal functions
*/
static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd);
static LRESULT WINAPI RegisterWindows9xService(BOOL set_service);
static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam);
static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty);
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam);
static int HookProc(int hc, HWND *hwnd, UINT *msg,
WPARAM *wParam, LPARAM *lParam);
#ifdef DBG
static VOID DbgPrintf(LPTSTR fmt, ...);
#endif
/* DllMain is invoked by every process in the entire system that is hooked
* by our window hooks, notably the tty processes' context, and by the user
* who wants tty messages (the app). Keep it light and simple.
*/
BOOL __declspec(dllexport) APIENTRY DllMain(HINSTANCE hModule, ULONG ulReason,
LPVOID pctx)
{
if (ulReason == DLL_PROCESS_ATTACH)
{
//hmodThis = hModule;
if (!hookwndmsg) {
origwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookOrigProc"));
hookwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookThunkWnd"));
hookwndmsg = RegisterWindowMessage("Win9xConHookMsg");
}
#ifdef DBG
// DbgPrintf("H ProcessAttach:%8.8x\r\n",
// GetCurrentProcessId());
#endif
}
else if ( ulReason == DLL_PROCESS_DETACH )
{
#ifdef DBG
// DbgPrintf("H ProcessDetach:%8.8x\r\n", GetCurrentProcessId());
#endif
if (monitor_hwnd)
SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
if (is_subclassed)
SendMessage(hwtty, hookwndmsg, 0, (LPARAM)hwtty);
if (hmodHook)
{
if (hhkGetMessage) {
UnhookWindowsHookEx(hhkGetMessage);
hhkGetMessage = NULL;
}
//if (hhkCallWndProc) {
// UnhookWindowsHookEx(hhkCallWndProc);
// hhkCallWndProc = NULL;
//}
FreeLibrary(hmodHook);
hmodHook = NULL;
}
if (is_service)
RegisterWindows9xService(FALSE);
if (hookwndmsg) {
GlobalDeleteAtom((ATOM)origwndprop);
GlobalDeleteAtom((ATOM)hookwndprop);
hookwndmsg = 0;
}
}
return TRUE;
}
/* This group of functions are provided for the service/console app
* to register itself a HandlerRoutine to accept tty or service messages
*/
/* Exported function that creates a Win9x 'service' via a hidden window,
* that notifies the process via the HandlerRoutine messages.
*/
BOOL __declspec(dllexport) WINAPI Windows9xServiceCtrlHandler(
PHANDLER_ROUTINE phandler,
LPCSTR name)
{
/* If we have not yet done so */
FreeConsole();
if (name)
{
DWORD tid;
HANDLE hThread;
/* NOTE: this is static so the module can continue to
* access these args while we go on to other things
*/
static tty_info tty;
tty.instance = GetModuleHandle(NULL);
tty.phandler = phandler;
tty.parent = NULL;
tty.name = name;
tty.type = 2;
RegisterWindows9xService(TRUE);
hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread,
(LPVOID)&tty, 0, &tid);
if (hThread)
{
CloseHandle(hThread);
return TRUE;
}
}
else /* remove */
{
if (monitor_hwnd)
SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
RegisterWindows9xService(FALSE);
return TRUE;
}
return FALSE;
}
/* Exported function that registers a HandlerRoutine to accept missing
* Win9x CTRL_EVENTs from the tty window, as NT does without a hassle.
* If add is 1 or 2, register the handler, if 2 also mark it as a service.
* If add is 0 deregister the handler, and unmark if a service
*/
BOOL __declspec(dllexport) WINAPI FixConsoleCtrlHandler(
PHANDLER_ROUTINE phandler,
INT add)
{
HWND parent;
if (add)
{
HANDLE hThread;
DWORD tid;
/* NOTE: this is static so the module can continue to
* access these args while we go on to other things
*/
static tty_info tty;
EnumWindows(EnumttyWindow, (LPARAM)&parent);
if (!parent) {
#ifdef DBG
DbgPrintf("A EnumttyWindow failed (%d)\r\n", GetLastError());
#endif
return FALSE;
}
tty.instance = GetModuleHandle(NULL);
tty.phandler = phandler;
tty.parent = parent;
tty.type = add;
if (add == 2) {
tty.name = "ttyService";
RegisterWindows9xService(TRUE);
}
else
tty.name = "ttyMonitor";
hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread,
(LPVOID)&tty, 0, &tid);
if (!hThread)
return FALSE;
CloseHandle(hThread);
hmodHook = LoadLibrary("Win9xConHook.dll");
if (hmodHook)
{
hhkGetMessage = SetWindowsHookEx(WH_GETMESSAGE,
(HOOKPROC)GetProcAddress(hmodHook, "GetMsgProc"), hmodHook, 0);
//hhkCallWndProc = SetWindowsHookEx(WH_CALLWNDPROC,
// (HOOKPROC)GetProcAddress(hmodHook, "CallWndProc"), hmodHook, 0);
}
return TRUE;
}
else /* remove */
{
if (monitor_hwnd) {
SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
}
if (hmodHook)
{
if (hhkGetMessage) {
UnhookWindowsHookEx(hhkGetMessage);
hhkGetMessage = NULL;
}
//if (hhkCallWndProc) {
// UnhookWindowsHookEx(hhkCallWndProc);
// hhkCallWndProc = NULL;
//}
FreeLibrary(hmodHook);
hmodHook = NULL;
}
if (is_service)
RegisterWindows9xService(FALSE);
return TRUE;
}
return FALSE;
}
/* The following internal helpers are only used within the app's context
*/
/* ttyConsoleCreateThread is the process that runs within the user app's
* context. It creates and pumps the messages of a hidden monitor window,
* watching for messages from the system, or the associated subclassed tty
* window. Things can happen in our context that can't be done from the
* tty's context, and visa versa, so the subclass procedure and this hidden
* window work together to make it all happen.
*/
static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty)
{
WNDCLASS wc;
MSG msg;
wc.style = CS_GLOBALCLASS;
wc.lpfnWndProc = ttyConsoleCtrlWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 8;
wc.hInstance = NULL;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
if (((tty_info*)tty)->parent)
wc.lpszClassName = "ttyConHookChild";
else
wc.lpszClassName = "ApacheWin95ServiceMonitor";
if (!RegisterClass(&wc)) {
#ifdef DBG
DbgPrintf("A proc %8.8x Error creating class %s (%d)\r\n",
GetCurrentProcessId(), wc.lpszClassName, GetLastError());
#endif
return 0;
}
/* Create an invisible window */
monitor_hwnd = CreateWindow(wc.lpszClassName, ((tty_info*)tty)->name,
WS_OVERLAPPED & ~WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL,
((tty_info*)tty)->instance, tty);
if (!monitor_hwnd) {
#ifdef DBG
DbgPrintf("A proc %8.8x Error creating window %s %s (%d)\r\n",
GetCurrentProcessId(), wc.lpszClassName,
((tty_info*)tty)->name, GetLastError());
#endif
return 0;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* Tag again as deleted, just in case we missed WM_DESTROY */
monitor_hwnd = NULL;
return 0;
}
/* This is the WndProc procedure for our invisible window.
* When our subclasssed tty window receives the WM_CLOSE, WM_ENDSESSION,
* or WM_QUERYENDSESSION messages, the message is dispatched to our hidden
* window (this message process), and we call the installed HandlerRoutine
* that was registered by the app.
*/
static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
if (msg == WM_CREATE)
{
tty_info *tty = (tty_info*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
SetWindowLong(hwnd, gwltty_phandler, (LONG)tty->phandler);
SetWindowLong(hwnd, gwltty_ttywnd, (LONG)tty->parent);
#ifdef DBG
DbgPrintf("A proc %8.8x created %8.8x %s for tty wnd %8.8x\r\n",
GetCurrentProcessId(), hwnd,
tty->name, tty->parent);
#endif
if (tty->parent) {
SetProp(tty->parent, hookwndprop, hwnd);
PostMessage(tty->parent, hookwndmsg,
tty->type, (LPARAM)tty->parent);
}
return 0;
}
else if (msg == WM_DESTROY)
{
HWND parent = (HWND)GetWindowLong(hwnd, gwltty_ttywnd);
#ifdef DBG
DbgPrintf("A proc %8.8x destroyed %8.8x ttyConHookChild\r\n",
GetCurrentProcessId(), hwnd);
#endif
if (parent) {
RemoveProp(parent, hookwndprop);
SendMessage(parent, hookwndmsg, 0, (LPARAM)parent);
}
monitor_hwnd = NULL;
}
else if (msg == WM_CLOSE)
{
PHANDLER_ROUTINE phandler =
(PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
LRESULT rv = phandler(CTRL_CLOSE_EVENT);
#ifdef DBG
DbgPrintf("A proc %8.8x invoked CTRL_CLOSE_EVENT "
"returning %d\r\n",
GetCurrentProcessId(), rv);
#endif
if (rv)
return !rv;
}
else if ((msg == WM_QUERYENDSESSION) || (msg == WM_ENDSESSION))
{
if (lParam & ENDSESSION_LOGOFF)
{
PHANDLER_ROUTINE phandler =
(PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
LRESULT rv = phandler(CTRL_LOGOFF_EVENT);
#ifdef DBG
DbgPrintf("A proc %8.8x invoked CTRL_LOGOFF_EVENT "
"returning %d\r\n",
GetCurrentProcessId(), rv);
#endif
if (rv)
return ((msg == WM_QUERYENDSESSION) ? rv : !rv);
}
else
{
PHANDLER_ROUTINE phandler =
(PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
LRESULT rv = phandler(CTRL_SHUTDOWN_EVENT);
#ifdef DBG
DbgPrintf("A proc %8.8x invoked CTRL_SHUTDOWN_EVENT "
"returning %d\r\n", GetCurrentProcessId(), rv);
#endif
if (rv)
return ((msg == WM_QUERYENDSESSION) ? rv : !rv);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
/* The following internal helpers are invoked by the hooked tty and our app
*/
/* Register or deregister the current process as a Windows9x style service.
* Experience shows this call is ignored across processes, so the second
* arg to RegisterServiceProcess (process group id) is effectively useless.
*/
static LRESULT WINAPI RegisterWindows9xService(BOOL set_service)
{
static HINSTANCE hkernel;
static DWORD (WINAPI *register_service_process)(DWORD, DWORD) = NULL;
BOOL rv;
if (set_service == is_service)
return 1;
#ifdef DBG
DbgPrintf("R %s proc %8.8x as a service\r\n",
set_service ? "installing" : "removing",
GetCurrentProcessId());
#endif
if (!register_service_process)
{
/* Obtain a handle to the kernel library */
hkernel = LoadLibrary("KERNEL32.DLL");
if (!hkernel)
return 0;
/* Find the RegisterServiceProcess function */
register_service_process = (DWORD (WINAPI *)(DWORD, DWORD))
GetProcAddress(hkernel, "RegisterServiceProcess");
if (register_service_process == NULL) {
FreeLibrary(hkernel);
return 0;
}
}
/* Register this process as a service */
rv = register_service_process(0, set_service != FALSE);
if (rv)
is_service = set_service;
if (!is_service)
{
/* Unload the kernel library */
FreeLibrary(hkernel);
register_service_process = NULL;
}
return rv;
}
/*
* This function only works when this process is the active process
* (e.g. once it is running a child process, it can no longer determine
* which console window is its own.)
*/
static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd)
{
char tmp[8];
if (GetClassName(wnd, tmp, sizeof(tmp)) && !strcmp(tmp, "tty"))
{
DWORD wndproc, thisproc = GetCurrentProcessId();
GetWindowThreadProcessId(wnd, &wndproc);
if (wndproc == thisproc) {
*((HWND*)retwnd) = wnd;
return FALSE;
}
}
return TRUE;
}
/* The remaining code all executes --in the tty's own process context--
*
* That means special attention must be paid to what it's doing...
*/
/* Subclass message process for the tty window
*
* This code -handles- WM_CLOSE, WM_ENDSESSION and WM_QUERYENDSESSION
* by dispatching them to the window identified by the hookwndprop
* property atom set against our window. Messages are then dispatched
* to origwndprop property atom we set against the window when we
* injected this subclass. This trick did not work with simply a hook.
*/
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
WNDPROC origproc = (WNDPROC) GetProp(hwnd, origwndprop);
if (!origproc)
return 0;
if (msg == WM_NCDESTROY
|| (msg == hookwndmsg && !LOWORD(wParam) && (HWND)lParam == hwnd))
{
if (is_subclassed) {
#ifdef DBG
DbgPrintf("W proc %08x hwnd:%08x Subclass removed\r\n",
GetCurrentProcessId(), hwnd);
#endif
if (is_service)
RegisterWindows9xService(FALSE);
SetWindowLong(hwnd, GWL_WNDPROC, (LONG)origproc);
RemoveProp(hwnd, origwndprop);
RemoveProp(hwnd, hookwndprop);
is_subclassed = FALSE;
//if (hmodLock)
// FreeLibrary(hmodLock);
//hmodLock = NULL;
}
}
else if (msg == WM_CLOSE || msg == WM_ENDSESSION
|| msg == WM_QUERYENDSESSION)
{
HWND child = (HWND)GetProp(hwnd, hookwndprop);
if (child) {
#ifdef DBG
DbgPrintf("W proc %08x hwnd:%08x forwarded msg:%d\r\n",
GetCurrentProcessId(), hwnd, msg);
#endif
return SendMessage(child, msg, wParam, lParam);
}
}
return CallWindowProc(origproc, hwnd, msg, wParam, lParam);
}
/* HookProc, once installed, is responsible for subclassing the system
* tty windows. It generally does nothing special itself, since
* research indicates that it cannot deal well with the messages we are
* interested in, that is, WM_CLOSE, WM_QUERYSHUTDOWN and WM_SHUTDOWN
* of the tty process.
*
* Respond and subclass only when a WM_NULL is received by the window.
*/
int HookProc(int hc, HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam)
{
if (is_tty == -1 && *hwnd)
{
char ttybuf[8];
HWND htty;
hwtty = *hwnd;
while (htty = GetParent(hwtty))
hwtty = htty;
is_tty = (GetClassName(hwtty, ttybuf, sizeof(ttybuf))
&& !strcmp(ttybuf, "tty"));
#ifdef DBG
if (is_tty)
DbgPrintf("H proc %08x tracking hwnd %08x\r\n",
GetCurrentProcessId(), hwtty);
#endif
}
if (*msg == hookwndmsg && *wParam && *lParam == (LPARAM)hwtty && is_tty)
{
WNDPROC origproc = (WNDPROC)GetWindowLong(hwtty, GWL_WNDPROC);
//char myname[MAX_PATH];
//if (GetModuleFileName(hmodThis, myname, sizeof(myname)))
// hmodLock = LoadLibrary(myname);
SetProp(hwtty, origwndprop, origproc);
SetWindowLong(hwtty, GWL_WNDPROC, (LONG)WndProc);
is_subclassed = TRUE;
#ifdef DBG
DbgPrintf("H proc %08x hwnd:%08x Subclassed\r\n",
GetCurrentProcessId(), hwtty);
#endif
if (LOWORD(*wParam) == 2)
RegisterWindows9xService(TRUE);
}
return -1;
}
/*
* PostMessage Hook:
*/
LRESULT __declspec(dllexport) CALLBACK GetMsgProc(INT hc, WPARAM wParam,
LPARAM lParam)
{
PMSG pmsg;
pmsg = (PMSG)lParam;
if (pmsg) {
int rv = HookProc(hc, &pmsg->hwnd, &pmsg->message,
&pmsg->wParam, &pmsg->lParam);
if (rv != -1)
return rv;
}
/*
* CallNextHookEx apparently ignores the hhook argument, so pass NULL
*/
return CallNextHookEx(NULL, hc, wParam, lParam);
}
/*
* SendMessage Hook:
*/
LRESULT __declspec(dllexport) CALLBACK CallWndProc(INT hc, WPARAM wParam,
LPARAM lParam)
{
PCWPSTRUCT pcwps = (PCWPSTRUCT)lParam;
if (pcwps) {
int rv = HookProc(hc, &pcwps->hwnd, &pcwps->message,
&pcwps->wParam, &pcwps->lParam);
if (rv != -1)
return rv;
}
/*
* CallNextHookEx apparently ignores the hhook argument, so pass NULL
*/
return CallNextHookEx(NULL, hc, wParam, lParam);
}
#ifdef DBG
VOID DbgPrintf(
LPTSTR fmt,
...
)
{
static HANDLE mutex;
va_list marker;
TCHAR szBuf[256];
DWORD t;
HANDLE gDbgOut;
va_start(marker, fmt);
wvsprintf(szBuf, fmt, marker);
va_end(marker);
if (!mutex)
mutex = CreateMutex(NULL, FALSE, "Win9xConHookDbgOut");
WaitForSingleObject(mutex, INFINITE);
gDbgOut = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);
WriteFile(gDbgOut, szBuf, strlen(szBuf), &t, NULL);
CloseHandle(gDbgOut);
ReleaseMutex(mutex);
}
#endif
#endif /* WIN32 */