You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
19 KiB
676 lines
19 KiB
|
|
/******************************Module*Header*******************************\
|
|
* Module Name: viewer.cxx
|
|
*
|
|
* Copyright (c) 2000 Microsoft Corporation
|
|
*
|
|
\**************************************************************************/
|
|
|
|
|
|
#include "precomp.hxx"
|
|
|
|
#include <tchar.h>
|
|
|
|
LRESULT CALLBACK ViewerWndProc(HWND, UINT, WPARAM, LPARAM);
|
|
|
|
|
|
typedef struct {
|
|
HWND hViewerWnd; // To be set by created thread
|
|
PDEBUG_CLIENT Client;
|
|
PSURF_INFO SurfInfo;
|
|
} ViewerThreadParams;
|
|
|
|
DWORD WINAPI ViewerThread(ViewerThreadParams *);
|
|
|
|
const _TCHAR szClassName[] = _T("KD GDI Viewer");
|
|
const _TCHAR szWindowName[] = _T("KD GDI Viewer");
|
|
ATOM gViewerAtom;
|
|
HBRUSH ghbrWhite;
|
|
HPEN ghBorderPen;
|
|
|
|
const LONG DEFAULT_SCALE = 2;
|
|
|
|
class ViewerManager
|
|
{
|
|
public:
|
|
ViewerManager(ULONG GrowLength = 4)
|
|
{
|
|
BeingDestroyed = FALSE;
|
|
Wnds = 0;
|
|
MaxWnds = 0;
|
|
phWndList = NULL;
|
|
GrowLen = (GrowLength == 0) ? 4 : GrowLength;
|
|
__try {
|
|
InitializeCriticalSection(&CritSect);
|
|
CritOk = TRUE;
|
|
Grow();
|
|
}
|
|
__except (STATUS_NO_MEMORY) {
|
|
CritOk = FALSE;
|
|
}
|
|
}
|
|
|
|
~ViewerManager()
|
|
{
|
|
if (CritOk) EnterCriticalSection(&CritSect);
|
|
BeingDestroyed = TRUE;
|
|
if (CritOk) LeaveCriticalSection(&CritSect);
|
|
|
|
DestroyAll();
|
|
// If we have Wnds left at this point all of them
|
|
// are now tracked as threads. Wait for each
|
|
// thread to completely finish.
|
|
if (Wnds)
|
|
{
|
|
DWORD WaitReturn;
|
|
DbgPrint("Waiting for remaining %lu threads...\n", Wnds);
|
|
WaitReturn = WaitForMultipleObjects(Wnds, (HANDLE *)phWndList, TRUE, INFINITE);
|
|
DbgPrint("WaitForMultipleObjects returned %lx.\n", WaitReturn);
|
|
while (Wnds-- > 0)
|
|
{
|
|
CloseHandle(phWndList[Wnds]);
|
|
DbgPrint("ViewerManager::~ViewerManager calling ExtRelease().\n");
|
|
ExtRelease();
|
|
}
|
|
}
|
|
|
|
HeapFree(hHeap, 0, phWndList);
|
|
if (CritOk) DeleteCriticalSection(&CritSect);
|
|
}
|
|
|
|
BOOL Grow();
|
|
|
|
BOOL Destroy(HWND);
|
|
|
|
void DestroyAll()
|
|
{
|
|
ULONG i = Wnds;
|
|
while (i-- > 0)
|
|
{
|
|
Destroy(phWndList[i]);
|
|
}
|
|
}
|
|
|
|
private:
|
|
ULONG Wnds;
|
|
ULONG MaxWnds;
|
|
HWND *phWndList;
|
|
HANDLE hHeap;
|
|
ULONG GrowLen;
|
|
BOOL BeingDestroyed;
|
|
BOOL CritOk;
|
|
CRITICAL_SECTION CritSect;
|
|
|
|
friend DWORD WINAPI ViewerThread(ViewerThreadParams *);
|
|
|
|
BOOL Track(HWND hWnd)
|
|
{
|
|
if (this == NULL || !CritOk) return FALSE;
|
|
|
|
BOOL bTracked = FALSE;
|
|
|
|
EnterCriticalSection(&CritSect);
|
|
|
|
if (!BeingDestroyed &&
|
|
((Wnds < MaxWnds) || Grow()))
|
|
{
|
|
DbgPrint("ViewerManager: Tracking %lx.\n", hWnd);
|
|
phWndList[Wnds++] = hWnd;
|
|
bTracked = TRUE;
|
|
}
|
|
|
|
LeaveCriticalSection(&CritSect);
|
|
|
|
return bTracked;
|
|
}
|
|
|
|
BOOL Untrack(HWND hWnd)
|
|
{
|
|
if (this == NULL || !CritOk) return FALSE;
|
|
|
|
BOOL bFound = FALSE;
|
|
|
|
EnterCriticalSection(&CritSect);
|
|
|
|
ULONG i = Wnds;
|
|
|
|
while (i-- > 0)
|
|
{
|
|
if (phWndList[i] == hWnd)
|
|
{
|
|
DbgPrint("ViewerManager: No longer tracking %lx.\n", hWnd);
|
|
phWndList[i] = phWndList[--Wnds];
|
|
phWndList[Wnds] = NULL;
|
|
bFound = TRUE;
|
|
if (!BeingDestroyed)
|
|
{
|
|
DbgPrint("ViewerManager::Untrack calling ExtRelease().\n");
|
|
ExtRelease();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
DbgPrint("ViewerManager::Untrack didn't find %lx.\n", hWnd);
|
|
|
|
LeaveCriticalSection(&CritSect);
|
|
|
|
return bFound;
|
|
}
|
|
|
|
};
|
|
|
|
BOOL ViewerManager::Grow()
|
|
{
|
|
if (MaxWnds > 0)
|
|
{
|
|
HWND *pNewList = (HWND *)HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, phWndList, (MaxWnds + GrowLen)*sizeof(HWND));
|
|
|
|
if (pNewList != NULL)
|
|
{
|
|
phWndList = pNewList;
|
|
MaxWnds += GrowLen;
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hHeap = GetProcessHeap();
|
|
|
|
if (hHeap)
|
|
{
|
|
phWndList = (HWND *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, GrowLen*sizeof(HWND));
|
|
if (phWndList != NULL)
|
|
{
|
|
MaxWnds = GrowLen;
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
DbgPrint("ViewerManager::Grow FAILED!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL ViewerManager::Destroy(HWND hWnd)
|
|
{
|
|
ULONG i = Wnds;
|
|
|
|
DbgPrint("Looking for window %lx in %lu entries.\n", hWnd, i);
|
|
|
|
while (i-- > 0)
|
|
{
|
|
if (phWndList[i] == hWnd)
|
|
{
|
|
DbgPrint("Destroying window %lx at entry %lu.\n", hWnd, i);
|
|
DWORD ThreadID = GetWindowThreadProcessId(hWnd, NULL);
|
|
HANDLE hThread;
|
|
if (hThread = OSCompat_OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, FALSE, ThreadID))
|
|
{
|
|
BOOL bCloseHandle = TRUE;
|
|
|
|
DWORD ExitCode = STILL_ACTIVE;
|
|
while (!PostMessage(hWnd, WM_DESTROY, 0, 0))
|
|
{
|
|
DbgPrint("Waiting on post msg to %lx...\n", hWnd);
|
|
Sleep(10);
|
|
if (GetExitCodeThread(hThread, &ExitCode) &&
|
|
ExitCode != STILL_ACTIVE)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check thread exit status
|
|
if (ExitCode == STILL_ACTIVE)
|
|
{
|
|
if (!GetExitCodeThread(hThread, &ExitCode))
|
|
{
|
|
DbgPrint("GetExitCodeThread returned error %lx.\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
// Give the thread a chance to exit
|
|
if (ExitCode == STILL_ACTIVE)
|
|
{
|
|
DWORD WaitReturn;
|
|
|
|
DbgPrint("Waiting for hThread: %lx, ThreadID: %lx, hWnd: %lx.\n", hThread, ThreadID, hWnd);
|
|
|
|
if (WAIT_OBJECT_0 != (WaitReturn = WaitForSingleObject(hThread, 100)))
|
|
{
|
|
DbgPrint("WaitForSingleObject returned %lx.\n", WaitReturn);
|
|
// If it hasn't exited and it called untrack
|
|
// to remove the hWnd we're concerned with,
|
|
// replace it with the thread handle so we may
|
|
// wait on it later.
|
|
EnterCriticalSection(&CritSect);
|
|
if (phWndList[i] == hWnd)
|
|
{
|
|
phWndList[i] = (HWND)hThread;
|
|
bCloseHandle = FALSE;
|
|
}
|
|
LeaveCriticalSection(&CritSect);
|
|
}
|
|
}
|
|
|
|
if (bCloseHandle)
|
|
{
|
|
// If the thread was still active, but the track entry
|
|
// has been removed, we have to wait for the thread to
|
|
// completely terminate.
|
|
if (ExitCode == STILL_ACTIVE)
|
|
{
|
|
DbgPrint("Inifinitely waiting for thread %lx to complete.\n", ThreadID);
|
|
WaitForSingleObject(hThread, INFINITE);
|
|
|
|
if (!GetExitCodeThread(hThread, &ExitCode))
|
|
{
|
|
DbgPrint("GetExitCodeThread returned error %lx.\n", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("Thread exit code was %lx.\n", ExitCode);
|
|
}
|
|
}
|
|
|
|
DbgPrint("Closing hThread: %lx\n", hThread);
|
|
CloseHandle(hThread);
|
|
|
|
if (BeingDestroyed)
|
|
{
|
|
DbgPrint("ViewerManager::Destroy calling ExtRelease().\n");
|
|
ExtRelease();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This really hurts.
|
|
// We have a tracked window, but we can't get
|
|
// information on it's thread, we have to stop
|
|
// tracking it.
|
|
|
|
DbgPrint("ViewerManager::Destroy: OpenThread returned error %lx!\n", GetLastError());
|
|
|
|
EnterCriticalSection(&CritSect);
|
|
if (phWndList[i] == hWnd)
|
|
{
|
|
phWndList[i] = phWndList[--Wnds];
|
|
phWndList[Wnds] = NULL;
|
|
}
|
|
LeaveCriticalSection(&CritSect);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
ViewerManager *ViewerMgr;
|
|
|
|
void ViewerInit()
|
|
{
|
|
if (ViewerMgr == NULL)
|
|
{
|
|
ViewerMgr = new ViewerManager;
|
|
|
|
if (ViewerMgr == NULL) return;
|
|
}
|
|
|
|
if (! ghbrWhite)
|
|
{
|
|
DbgPrint("ViewerInit: Creating white brush\n");
|
|
ghbrWhite = CreateSolidBrush(RGB(0xFF,0xFF,0xFF));
|
|
DbgPrint("ViewerInit: Created brush %lx\n", ghbrWhite);
|
|
}
|
|
|
|
if (! ghBorderPen)
|
|
{
|
|
DbgPrint("ViewerInit: Creating redish pen\n");
|
|
ghBorderPen = CreatePen(PS_SOLID, 1, RGB(0xF0, 0x00, 0x3F));
|
|
DbgPrint("ViewerInit: Created pen %lx\n", ghBorderPen);
|
|
}
|
|
|
|
if (! gViewerAtom)
|
|
{
|
|
WNDCLASSEX wcex;
|
|
|
|
DbgPrint("ViewerInit: Registering Class\n");
|
|
DbgPrint("ViewerInit: ghDllInst = %lx\n", ghDllInst);
|
|
|
|
wcex.cbSize = sizeof(wcex);
|
|
wcex.style = CS_VREDRAW | CS_HREDRAW;
|
|
wcex.lpfnWndProc = ViewerWndProc;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = 0;
|
|
wcex.hInstance = ghDllInst;
|
|
wcex.hIcon = NULL;
|
|
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
|
|
wcex.hbrBackground = (HBRUSH)( COLOR_WINDOW+1 );
|
|
wcex.lpszMenuName = NULL;
|
|
wcex.lpszClassName = szClassName;
|
|
wcex.hIconSm = NULL;
|
|
|
|
gViewerAtom = RegisterClassEx( &wcex );
|
|
}
|
|
}
|
|
|
|
void ViewerExit()
|
|
{
|
|
if (ViewerMgr != NULL)
|
|
{
|
|
delete ViewerMgr;
|
|
ViewerMgr = NULL;
|
|
}
|
|
|
|
if (gViewerAtom)
|
|
{
|
|
DbgPrint("ViewerExit: Unregistering Class\n");
|
|
UnregisterClass((LPCSTR)gViewerAtom, 0);
|
|
gViewerAtom = 0;
|
|
}
|
|
|
|
if (ghBorderPen)
|
|
{
|
|
DbgPrint("ViewerExit: Deleting border pen\n");
|
|
DeleteObject(ghBorderPen);
|
|
ghBorderPen = NULL;
|
|
}
|
|
|
|
if (ghbrWhite)
|
|
{
|
|
DbgPrint("ViewerInit: Deleting white brush\n");
|
|
DeleteObject(ghbrWhite);
|
|
ghbrWhite = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
CALLBACK
|
|
ViewerWndEnumProc(
|
|
HWND hWnd,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
HWND *phWndParent = (HWND *)lParam;
|
|
|
|
DbgPrint("Found hWnd %lx.\n", hWnd);
|
|
|
|
if (*phWndParent == NULL)
|
|
{
|
|
*phWndParent = hWnd;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
ViewerThread(
|
|
ViewerThreadParams *Params
|
|
)
|
|
{
|
|
HWND hWnd;
|
|
BOOL bGetMsg;
|
|
MSG msg;
|
|
_TCHAR ViewerWndName[sizeof(szWindowName) + sizeof(Params->SurfInfo->SurfName) + 20];
|
|
_TCHAR *pszName = Params->SurfInfo->SurfName;
|
|
|
|
if (!pszName[0]) pszName = _T("UNAMED");
|
|
|
|
_stprintf(ViewerWndName, "%s: %s (%ldx%ldx%hubpp)", szWindowName, pszName,
|
|
Params->SurfInfo->Width,
|
|
Params->SurfInfo->Height,
|
|
Params->SurfInfo->BitsPixel);
|
|
|
|
|
|
hWnd = CreateWindowEx(WS_EX_LEFT,
|
|
(LPCSTR)gViewerAtom,
|
|
ViewerWndName,
|
|
WS_OVERLAPPEDWINDOW,// | WS_HSCROLL | WS_VSCROLL,
|
|
0,
|
|
0,
|
|
Params->SurfInfo->Width*DEFAULT_SCALE+10,//32,
|
|
Params->SurfInfo->Height*DEFAULT_SCALE+29,//48,
|
|
NULL,
|
|
NULL,
|
|
ghDllInst,
|
|
Params->SurfInfo // lParam passed to WM_CREATE handler
|
|
);
|
|
|
|
if (hWnd)
|
|
{
|
|
ViewerMgr->Track(hWnd);
|
|
|
|
Params->hViewerWnd = hWnd; // Params may no longer be valid.
|
|
|
|
ShowWindow(hWnd, SW_SHOWDEFAULT);
|
|
UpdateWindow(hWnd);
|
|
|
|
while( (bGetMsg = GetMessage(&msg, NULL, 0, 0 )) != 0 )
|
|
{
|
|
if (bGetMsg == -1)
|
|
{
|
|
DbgPrint("ViewerThread exiting due to GetMessage error 0x%lx.\n",
|
|
GetLastError());
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
TranslateMessage( &msg );
|
|
DispatchMessage( &msg );
|
|
}
|
|
}
|
|
|
|
DbgPrint("ViewerThread exiting properly.\n");
|
|
|
|
ViewerMgr->Untrack(hWnd);
|
|
}
|
|
else
|
|
{
|
|
DbgPrint("CreateWindow returned error %lx.\n", GetLastError());
|
|
|
|
msg.wParam = -1;
|
|
}
|
|
|
|
DbgPrint("ViewerThread calling ExitThread().\n");
|
|
|
|
ExitThread((DWORD)msg.wParam);
|
|
}
|
|
|
|
|
|
DWORD
|
|
CreateViewer(
|
|
PDEBUG_CLIENT Client,
|
|
PSURF_INFO SurfInfo
|
|
)
|
|
{
|
|
ViewerThreadParams NewThreadParams = { NULL, Client, SurfInfo };
|
|
HRESULT Status;
|
|
|
|
// Reference Debug Client for ViewerThread
|
|
// since dbgeng/dbghelp aren't thread safe.
|
|
// ViewerManager will release client in a safe manner.
|
|
if ((Status = ExtQuery(Client)) != S_OK) return 0;
|
|
|
|
HWND hWndParent = NULL;
|
|
EnumThreadWindows(GetCurrentThreadId(), (WNDENUMPROC)ViewerWndEnumProc, (LPARAM)&hWndParent);
|
|
|
|
HANDLE hThread;
|
|
DWORD ThreadID = 0;
|
|
hThread = CreateThread(NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE)ViewerThread,
|
|
&NewThreadParams,
|
|
0,
|
|
&ThreadID);
|
|
|
|
if (hThread)
|
|
{
|
|
while (NewThreadParams.hViewerWnd == NULL)
|
|
{
|
|
DWORD ExitCode = 0;
|
|
if (!GetExitCodeThread(hThread, &ExitCode))
|
|
DbgPrint("GetExitCodeThread returned error %lx.\n", GetLastError());
|
|
if (ExitCode != STILL_ACTIVE)
|
|
{
|
|
ThreadID = 0;
|
|
break;
|
|
}
|
|
|
|
SleepEx(10, TRUE);
|
|
}
|
|
|
|
CloseHandle(hThread);
|
|
}
|
|
|
|
if (ThreadID == 0)
|
|
{
|
|
ExtRelease();
|
|
}
|
|
|
|
return ThreadID;
|
|
}
|
|
|
|
|
|
// DelPropProc is a callback function
|
|
// that deletes a window property.
|
|
|
|
BOOL CALLBACK DelPropProc(
|
|
HWND hwndSubclass, // handle of window with property
|
|
LPCSTR lpszString, // property string or atom
|
|
HANDLE hData) // data handle
|
|
{
|
|
RemoveProp(hwndSubclass, lpszString);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
LRESULT
|
|
CALLBACK
|
|
ViewerWndProc(
|
|
HWND hWnd,
|
|
UINT msg,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
// DbgPrint("ViewerWndProc(%lx, %lx, , )\n", hWnd, msg);
|
|
|
|
switch( msg )
|
|
{
|
|
case WM_CREATE:
|
|
{
|
|
LPCREATESTRUCT CreateStruct = (LPCREATESTRUCT)lParam;
|
|
PSURF_INFO SurfInfo = (PSURF_INFO) CreateStruct->lpCreateParams;
|
|
|
|
DbgPrint("ViewerWndProc: WM_CREATE\n");
|
|
|
|
if (SurfInfo)
|
|
{
|
|
SetProp(hWnd, "hBitmap", SurfInfo->hBitmap);
|
|
SetProp(hWnd, "xOrigin", LongToHandle(SurfInfo->xOrigin));
|
|
SetProp(hWnd, "yOrigin", LongToHandle(SurfInfo->yOrigin));
|
|
SetProp(hWnd, "Width", LongToHandle(SurfInfo->Width));
|
|
SetProp(hWnd, "Height", LongToHandle(SurfInfo->Height));
|
|
SetProp(hWnd, "BPP", LongToHandle(SurfInfo->BitsPixel));
|
|
SetProp(hWnd, "Scale", LongToHandle(DEFAULT_SCALE));
|
|
// SetProp(hWnd, "", SurfInfo->);
|
|
}
|
|
else
|
|
{
|
|
ExtErr("ViewerWindow created with NULL PSURF_INFO.\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case WM_KEYDOWN:
|
|
if (wParam == VK_DOWN || wParam == VK_UP)
|
|
{
|
|
LONG Scale = HandleToLong(GetProp(hWnd, "Scale"));
|
|
if (wParam == VK_DOWN)
|
|
{
|
|
if (Scale > 1)
|
|
{
|
|
SetProp(hWnd, "Scale", LongToHandle((Scale-1)));
|
|
InvalidateRect(hWnd, NULL, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Scale < 16)
|
|
{
|
|
SetProp(hWnd, "Scale", LongToHandle((Scale+1)));
|
|
InvalidateRect(hWnd, NULL, TRUE);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc;
|
|
HBITMAP hBitmapOrg;
|
|
HBRUSH hBrushOrg;
|
|
HPEN hPenOrg;
|
|
|
|
BeginPaint(hWnd, &ps);
|
|
|
|
hdc = CreateCompatibleDC(ps.hdc);
|
|
hBitmapOrg = (HBITMAP)SelectObject(hdc, (HBITMAP)GetProp(hWnd, "hBitmap"));
|
|
if (hBitmapOrg == NULL)
|
|
{
|
|
DbgPrint("Error from SelectObject(, HBITMAP): %lx\n", GetLastError());
|
|
}
|
|
hBrushOrg = (HBRUSH)SelectObject(ps.hdc, ghbrWhite);
|
|
hPenOrg = (HPEN)SelectObject(ps.hdc, ghBorderPen);
|
|
LONG xOrigin = HandleToLong(GetProp(hWnd, "xOrigin"));
|
|
LONG yOrigin = HandleToLong(GetProp(hWnd, "yOrigin"));
|
|
LONG Width = HandleToLong(GetProp(hWnd, "Width"));
|
|
LONG Height = HandleToLong(GetProp(hWnd, "Height"));
|
|
LONG Scale = HandleToLong(GetProp(hWnd, "Scale"));
|
|
Rectangle(ps.hdc, 0, 0, Width*Scale+2, Height*Scale+2);
|
|
if (!StretchBlt(ps.hdc, 1, 1, Width*Scale, Height*Scale, hdc, xOrigin, yOrigin, Width, Height, SRCCOPY))
|
|
{
|
|
DbgPrint("Error from StrectBlt): %lx\n", GetLastError());
|
|
}
|
|
SelectObject(ps.hdc, hPenOrg);
|
|
SelectObject(ps.hdc, hBrushOrg);
|
|
SelectObject(hdc, hBitmapOrg);
|
|
DeleteDC(hdc);
|
|
|
|
EndPaint(hWnd, &ps);
|
|
}
|
|
return DefWindowProc( hWnd, msg, wParam, lParam );
|
|
|
|
case WM_DESTROY:
|
|
|
|
DbgPrint("ViewerWndProc: WM_DESTROY\n");
|
|
|
|
DeleteObject((HBITMAP)GetProp(hWnd, "hBitmap"));
|
|
|
|
EnumPropsEx(hWnd, (PROPENUMPROCEX)DelPropProc, NULL);
|
|
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
default:
|
|
// DbgPrint("ViewerWndProc: unhandled msg %lx\n", msg);
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc( hWnd, msg, wParam, lParam );
|
|
}
|
|
|
|
|