VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxService/VBoxService.cpp@ 3109

Last change on this file since 3109 was 3109, checked in by vboxsync, 18 years ago

Seriously annoying debug printf

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.9 KB
Line 
1/** @file
2 * VBoxService - Guest Additions Service
3 */
4
5/*
6 * Copyright (C) 2006-2007 innotek GmbH
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.virtualbox.org. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License as published by the Free Software Foundation,
12 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
13 * distribution. VirtualBox OSE is distributed in the hope that it will
14 * be useful, but WITHOUT ANY WARRANTY of any kind.
15 *
16 * If you received this file as part of a commercial VirtualBox
17 * distribution, then only the terms of your commercial VirtualBox
18 * license agreement apply instead of the previous paragraph.
19 *
20 */
21
22#include "VBoxService.h"
23#include "resource.h"
24
25// #define DEBUG_DISPLAY_CHANGE
26
27/* global variables */
28HANDLE gVBoxDriver;
29HANDLE gStopSem;
30SERVICE_STATUS gVBoxServiceStatus;
31SERVICE_STATUS_HANDLE gVBoxServiceStatusHandle;
32HINSTANCE gInstance;
33HWND gToolWindow;
34
35
36/* prototypes */
37VOID DisplayChangeThread(void *dummy);
38LRESULT CALLBACK VBoxToolWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
39
40
41VOID SvcDebugOut2(LPSTR String, ...)
42{
43 va_list va;
44
45 va_start(va, String);
46
47 CHAR Buffer[1024];
48 if (strlen(String) < 1000)
49 {
50 vsprintf (Buffer, String, va);
51
52 OutputDebugStringA(Buffer);
53
54#ifdef DEBUG_DISPLAY_CHANGE
55 FILE *f = fopen ("vboxservice.log", "ab");
56 if (f)
57 {
58 fprintf (f, "%s", Buffer);
59 fclose (f);
60 }
61#endif /* DEBUG_DISPLAY_CHANGE */
62 }
63
64 va_end (va);
65}
66
67#ifdef DEBUG_DISPLAY_CHANGE
68#define DDCLOG(a) do { SvcDebugOut2 a; } while (0)
69#else
70#define DDCLOG(a) do {} while (0)
71#endif /* DEBUG_DISPLAY_CHANGE */
72
73
74/**
75 * Helper function to send a message to WinDbg
76 *
77 * @param String message string
78 * @param Status error code, will be inserted into String's placeholder
79 */
80VOID SvcDebugOut(LPSTR String, DWORD Status)
81{
82 SvcDebugOut2(String, Status);
83}
84
85/* The shared clipboard service prototypes. */
86int VBoxClipboardInit (const VBOXSERVICEENV *pEnv, void **ppInstance, bool *pfStartThread);
87unsigned __stdcall VBoxClipboardThread (void *pInstance);
88void VBoxClipboardDestroy (const VBOXSERVICEENV *pEnv, void *pInstance);
89
90/* The service table. */
91static VBOXSERVICEINFO vboxServiceTable[] =
92{
93 {
94 "Shared Clipboard",
95 VBoxClipboardInit,
96 VBoxClipboardThread,
97 VBoxClipboardDestroy
98 },
99 {
100 NULL
101 }
102};
103
104static int vboxStartServices (VBOXSERVICEENV *pEnv, VBOXSERVICEINFO *pTable)
105{
106 pEnv->hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
107
108 if (!pEnv->hStopEvent)
109 {
110 /* Could not create event. */
111 return VERR_NOT_SUPPORTED;
112 }
113
114 while (pTable->pszName)
115 {
116 SvcDebugOut2 ("Starting %s...\n", pTable->pszName);
117
118 int rc = VINF_SUCCESS;
119
120 bool fStartThread = false;
121
122 pTable->hThread = (HANDLE)0;
123 pTable->pInstance = NULL;
124 pTable->fStarted = false;
125
126 if (pTable->pfnInit)
127 {
128 rc = pTable->pfnInit (pEnv, &pTable->pInstance, &fStartThread);
129 }
130
131 if (VBOX_FAILURE (rc))
132 {
133 SvcDebugOut2 ("Failed to initialize rc = %Vrc.\n", rc);
134 }
135 else
136 {
137 if (pTable->pfnThread && fStartThread)
138 {
139 unsigned threadid;
140
141 pTable->hThread = (HANDLE)_beginthreadex (NULL, /* security */
142 0, /* stacksize */
143 pTable->pfnThread,
144 pTable->pInstance,
145 0, /* initflag */
146 &threadid);
147
148 if (pTable->hThread == (HANDLE)(0))
149 {
150 rc = VERR_NOT_SUPPORTED;
151 }
152 }
153
154 if (VBOX_FAILURE (rc))
155 {
156 SvcDebugOut2 ("Failed to start the thread.\n");
157
158 if (pTable->pfnDestroy)
159 {
160 pTable->pfnDestroy (pEnv, pTable->pInstance);
161 }
162 }
163 else
164 {
165 pTable->fStarted = true;
166 }
167 }
168
169 /* Advance to next table element. */
170 pTable++;
171 }
172
173 return VINF_SUCCESS;
174}
175
176static void vboxStopServices (VBOXSERVICEENV *pEnv, VBOXSERVICEINFO *pTable)
177{
178 if (!pEnv->hStopEvent)
179 {
180 return;
181 }
182
183 /* Signal to all threads. */
184 SetEvent(pEnv->hStopEvent);
185
186 while (pTable->pszName)
187 {
188 if (pTable->fStarted)
189 {
190 if (pTable->pfnThread)
191 {
192 /* There is a thread, wait for termination. */
193 WaitForSingleObject(pTable->hThread, INFINITE);
194
195 CloseHandle (pTable->hThread);
196 pTable->hThread = 0;
197 }
198
199 if (pTable->pfnDestroy)
200 {
201 pTable->pfnDestroy (pEnv, pTable->pInstance);
202 }
203
204 pTable->fStarted = false;
205 }
206
207 /* Advance to next table element. */
208 pTable++;
209 }
210
211 CloseHandle (pEnv->hStopEvent);
212}
213
214
215void WINAPI VBoxServiceStart(void)
216{
217 SvcDebugOut2("VBoxService: Start\n");
218
219 VBOXSERVICEENV svcEnv;
220
221 DWORD status = NO_ERROR;
222
223 /* open VBox guest driver */
224 gVBoxDriver = CreateFile(VBOXGUEST_DEVICE_NAME,
225 GENERIC_READ | GENERIC_WRITE,
226 FILE_SHARE_READ | FILE_SHARE_WRITE,
227 NULL,
228 OPEN_EXISTING,
229 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
230 NULL);
231 if (gVBoxDriver == INVALID_HANDLE_VALUE)
232 {
233 SvcDebugOut("VBoxService: could not open VBox Guest Additions driver! rc = %d\n", GetLastError());
234 status = ERROR_GEN_FAILURE;
235 }
236
237 SvcDebugOut2("VBoxService: Driver h %p, st %p\n", gVBoxDriver, status);
238
239 if (status == NO_ERROR)
240 {
241 /* create a custom window class */
242 WNDCLASS windowClass = {0};
243 windowClass.style = CS_NOCLOSE;
244 windowClass.lpfnWndProc = (WNDPROC)VBoxToolWndProc;
245 windowClass.hInstance = gInstance;
246 windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
247 windowClass.lpszClassName = "VirtualBoxTool";
248 if (!RegisterClass(&windowClass))
249 status = GetLastError();
250 }
251
252 SvcDebugOut2("VBoxService: Class st %p\n", status);
253
254 if (status == NO_ERROR)
255 {
256 /* create our window */
257 gToolWindow = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
258 "VirtualBoxTool", "VirtualBoxTool",
259 WS_POPUPWINDOW,
260 -200, -200, 100, 100, NULL, NULL, gInstance, NULL);
261 if (!gToolWindow)
262 status = GetLastError();
263 else
264 {
265 /* move the window beneach the mouse pointer so that we get access to it */
266 POINT mousePos;
267 GetCursorPos(&mousePos);
268 SetWindowPos(gToolWindow, HWND_TOPMOST, mousePos.x - 10, mousePos.y - 10, 0, 0,
269 SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
270 /* change the mouse pointer so that we can go for a hardware shape */
271 SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
272 SetCursor(LoadCursor(NULL, IDC_ARROW));
273 /* move back our tool window */
274 SetWindowPos(gToolWindow, HWND_TOPMOST, -200, -200, 0, 0,
275 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
276 }
277 }
278
279 SvcDebugOut2("VBoxService: Window h %p, st %p\n", gToolWindow, status);
280
281 if (status == NO_ERROR)
282 {
283 gStopSem = CreateEvent(NULL, TRUE, FALSE, NULL);
284 if (gStopSem == NULL)
285 {
286 SvcDebugOut("VBoxService: CreateEvent failed: rc = %d\n", GetLastError());
287 return;
288 }
289 }
290
291 /*
292 * Start services listed in the vboxServiceTable.
293 */
294 svcEnv.hInstance = gInstance;
295 svcEnv.hDriver = gVBoxDriver;
296
297 if (status == NO_ERROR)
298 {
299 int rc = vboxStartServices (&svcEnv, vboxServiceTable);
300
301 if (VBOX_FAILURE (rc))
302 {
303 status = ERROR_GEN_FAILURE;
304 }
305 }
306
307 /* create display change thread */
308 HANDLE hDisplayChangeThread;
309 if (status == NO_ERROR)
310 {
311 hDisplayChangeThread = (HANDLE)_beginthread(DisplayChangeThread, 0, NULL);
312 if ((int)hDisplayChangeThread == -1L)
313 status = ERROR_GEN_FAILURE;
314 }
315
316 SvcDebugOut2("VBoxService: hDisplayChangeThread h %p, st %p\n", hDisplayChangeThread, status);
317
318 /* terminate service if something went wrong */
319 if (status != NO_ERROR)
320 {
321 vboxStopServices (&svcEnv, vboxServiceTable);
322 return;
323 }
324
325 BOOL fTrayIconCreated = false;
326
327 /* prepare the system tray icon */
328 NOTIFYICONDATA ndata;
329 memset (&ndata, 0, sizeof (ndata));
330 ndata.cbSize = NOTIFYICONDATA_V1_SIZE; // sizeof(NOTIFYICONDATA);
331 ndata.hWnd = gToolWindow;
332 ndata.uID = 2000;
333 ndata.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
334 ndata.uCallbackMessage = WM_USER;
335 ndata.hIcon = LoadIcon(gInstance, MAKEINTRESOURCE(IDI_VIRTUALBOX));
336 sprintf(ndata.szTip, "innotek VirtualBox Guest Additions %d.%d.%d", VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD);
337
338 SvcDebugOut2("VBoxService: ndata.hWnd %08X, ndata.hIcon = %p\n", ndata.hWnd, ndata.hIcon);
339
340 /*
341 * Main execution loop
342 * Wait for the stop semaphore to be posted or a window event to arrive
343 */
344 while(true)
345 {
346 DWORD waitResult = MsgWaitForMultipleObjectsEx(1, &gStopSem, 500, QS_ALLINPUT, 0);
347 if (waitResult == WAIT_OBJECT_0)
348 {
349 SvcDebugOut2("VBoxService: exit\n");
350 /* exit */
351 break;
352 }
353 else if (waitResult == WAIT_OBJECT_0 + 1)
354 {
355 /* a window message, handle it */
356 MSG msg;
357 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
358 {
359 SvcDebugOut2("VBoxService: msg %p\n", msg.message);
360 if (msg.message == WM_QUIT)
361 {
362 SvcDebugOut2("VBoxService: WM_QUIT!\n");
363 SetEvent(gStopSem);
364 continue;
365 }
366 TranslateMessage(&msg);
367 DispatchMessage(&msg);
368 }
369 }
370 else /* timeout */
371 {
372#ifndef DEBUG_sandervl
373 SvcDebugOut2("VBoxService: timed out\n");
374#endif
375 /* we might have to repeat this operation because the shell might not be loaded yet */
376 if (!fTrayIconCreated)
377 {
378 fTrayIconCreated = Shell_NotifyIcon(NIM_ADD, &ndata);
379 SvcDebugOut2("VBoxService: fTrayIconCreated = %d, err %08X\n", fTrayIconCreated, GetLastError ());
380 }
381 }
382 }
383
384 SvcDebugOut("VBoxService: returned from main loop, exiting...\n", 0);
385
386 /* remove the system tray icon */
387 Shell_NotifyIcon(NIM_DELETE, &ndata);
388
389 SvcDebugOut("VBoxService: waiting for display change thread...\n", 0);
390
391 /* wait for the display change thread to terminate */
392 WaitForSingleObject(hDisplayChangeThread, INFINITE);
393
394 vboxStopServices (&svcEnv, vboxServiceTable);
395
396 SvcDebugOut("VBoxService: destroying tool window...\n", 0);
397
398 /* destroy the tool window */
399 DestroyWindow(gToolWindow);
400
401 UnregisterClass("VirtualBoxTool", gInstance);
402
403 CloseHandle(gVBoxDriver);
404 CloseHandle(gStopSem);
405
406 SvcDebugOut("VBoxService: leaving service main function\n", 0);
407
408 return;
409}
410
411
412/**
413 * Main function
414 */
415int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
416{
417 SvcDebugOut2("VBoxService: WinMain\n");
418 gInstance = hInstance;
419 VBoxServiceStart ();
420}
421
422static bool isVBoxDisplayDriverActive (void)
423{
424 bool result = false;
425
426 DISPLAY_DEVICE dispDevice;
427
428 FillMemory(&dispDevice, sizeof(DISPLAY_DEVICE), 0);
429
430 dispDevice.cb = sizeof(DISPLAY_DEVICE);
431
432 INT devNum = 0;
433
434 while (EnumDisplayDevices(NULL,
435 devNum,
436 &dispDevice,
437 0))
438 {
439 DDCLOG(("DevNum:%d\nName:%s\nString:%s\nID:%s\nKey:%s\nFlags=%08X\n\n",
440 devNum,
441 &dispDevice.DeviceName[0],
442 &dispDevice.DeviceString[0],
443 &dispDevice.DeviceID[0],
444 &dispDevice.DeviceKey[0],
445 dispDevice.StateFlags));
446
447 if (dispDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
448 {
449 DDCLOG(("Primary device.\n"));
450
451 if (strcmp(&dispDevice.DeviceString[0], "VirtualBox Graphics Adapter") == 0)
452 {
453 DDCLOG(("VBox display driver is active.\n"));
454 result = true;
455 }
456
457 break;
458 }
459
460 FillMemory(&dispDevice, sizeof(DISPLAY_DEVICE), 0);
461
462 dispDevice.cb = sizeof(DISPLAY_DEVICE);
463
464 devNum++;
465 }
466
467 return result;
468}
469
470/**
471 * Thread function to wait for and process display change
472 * requests
473 */
474VOID DisplayChangeThread(void *dummy)
475{
476 bool fTerminate = false;
477 VBoxGuestFilterMaskInfo maskInfo;
478 DWORD cbReturned;
479
480 maskInfo.u32OrMask = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
481 maskInfo.u32NotMask = 0;
482 if (DeviceIoControl (gVBoxDriver, IOCTL_VBOXGUEST_CTL_FILTER_MASK, &maskInfo, sizeof (maskInfo), NULL, 0, &cbReturned, NULL))
483 {
484 DDCLOG(("VBoxService: DeviceIOControl(CtlMask - or) succeeded\n"));
485 }
486 else
487 {
488 SvcDebugOut2("VBoxService: DeviceIOControl(CtlMask) failed, DisplayChangeThread exited\n");
489 return;
490 }
491
492 do
493 {
494 /* wait for a display change event */
495 VBoxGuestWaitEventInfo waitEvent;
496 waitEvent.u32TimeoutIn = 100;
497 waitEvent.u32EventMaskIn = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
498 if (DeviceIoControl(gVBoxDriver, IOCTL_VBOXGUEST_WAITEVENT, &waitEvent, sizeof(waitEvent), &waitEvent, sizeof(waitEvent), &cbReturned, NULL))
499 {
500 DDCLOG(("VBoxService: DeviceIOControl succeded\n"));
501
502 /* are we supposed to stop? */
503 if (WaitForSingleObject(gStopSem, 0) == WAIT_OBJECT_0)
504 break;
505
506 DDCLOG(("VBoxService: checking event\n"));
507
508 /* did we get the right event? */
509 if (waitEvent.u32EventFlagsOut & VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST)
510 {
511 DDCLOG(("VBoxService: going to get display change information.\n"));
512
513 /* We got at least one event. Read the requested resolution
514 * and try to set it until success. New events will not be seen
515 * but a new resolution will be read in this poll loop.
516 */
517 for (;;)
518 {
519 /* get the display change request */
520 VMMDevDisplayChangeRequest displayChangeRequest = {0};
521 displayChangeRequest.header.size = sizeof(VMMDevDisplayChangeRequest);
522 displayChangeRequest.header.version = VMMDEV_REQUEST_HEADER_VERSION;
523 displayChangeRequest.header.requestType = VMMDevReq_GetDisplayChangeRequest;
524 displayChangeRequest.eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
525 if (DeviceIoControl(gVBoxDriver, IOCTL_VBOXGUEST_VMMREQUEST, &displayChangeRequest, sizeof(displayChangeRequest),
526 &displayChangeRequest, sizeof(displayChangeRequest), &cbReturned, NULL))
527 {
528 DDCLOG(("VBoxService: VMMDevReq_GetDisplayChangeRequest: %dx%dx%d\n", displayChangeRequest.xres, displayChangeRequest.yres, displayChangeRequest.bpp));
529
530 /*
531 * Only try to change video mode if the active display driver is VBox additions.
532 */
533 if (isVBoxDisplayDriverActive ())
534 {
535 DEVMODE devMode;
536
537 /* get the current screen setup */
538 if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode))
539 {
540 SvcDebugOut2("VBoxService: Current mode: %dx%dx%d\n", devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel);
541
542 /* Horizontal resolution must be a multiple of 8, round down. */
543 displayChangeRequest.xres &= 0xfff8;
544
545 /* Check whether a mode reset or a change is requested. */
546 if (displayChangeRequest.xres || displayChangeRequest.yres || displayChangeRequest.bpp)
547 {
548 /* A change is requested.
549 * Set values which are not to be changed to the current values.
550 */
551 if (!displayChangeRequest.xres)
552 displayChangeRequest.xres = devMode.dmPelsWidth;
553 if (!displayChangeRequest.yres)
554 displayChangeRequest.yres = devMode.dmPelsHeight;
555 if (!displayChangeRequest.bpp)
556 displayChangeRequest.bpp = devMode.dmBitsPerPel;
557 }
558 else
559 {
560 /* All zero values means a forced mode reset. Do nothing. */
561 }
562
563 /* Verify that the mode is indeed changed. */
564 if ( devMode.dmPelsWidth == displayChangeRequest.xres
565 && devMode.dmPelsHeight == displayChangeRequest.yres
566 && devMode.dmBitsPerPel == displayChangeRequest.bpp)
567 {
568 SvcDebugOut2("VBoxService: already at desired resolution.\n");
569 break;
570 }
571
572 // without this, Windows will not ask the miniport for its
573 // mode table but uses an internal cache instead
574 DEVMODE tempDevMode = {0};
575 tempDevMode.dmSize = sizeof(DEVMODE);
576 EnumDisplaySettings(NULL, 0xffffff, &tempDevMode);
577
578 /* adjust the values that are supposed to change */
579 if (displayChangeRequest.xres)
580 devMode.dmPelsWidth = displayChangeRequest.xres;
581 if (displayChangeRequest.yres)
582 devMode.dmPelsHeight = displayChangeRequest.yres;
583 if (displayChangeRequest.bpp)
584 devMode.dmBitsPerPel = displayChangeRequest.bpp;
585
586 DDCLOG(("VBoxService: setting the new mode %dx%dx%d\n", devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel));
587
588 /* set the new mode */
589 LONG status;
590 status = ChangeDisplaySettings(&devMode, CDS_UPDATEREGISTRY);
591 if (status != DISP_CHANGE_SUCCESSFUL)
592 {
593 SvcDebugOut("VBoxService: error from ChangeDisplaySettings: %d\n", status);
594
595 if (status == DISP_CHANGE_BADMODE)
596 {
597 /* Our driver can not set the requested mode. Stop trying. */
598 break;
599 }
600 }
601 else
602 {
603 /* Successfully set new video mode. */
604 break;
605 }
606 }
607 else
608 {
609 SvcDebugOut("VBoxService: error from EnumDisplaySettings\n", 0);
610 }
611 }
612 else
613 {
614 SvcDebugOut2("VBoxService: vboxDisplayDriver is not active.\n", 0);
615 }
616
617 /* Retry the change a bit later. */
618 /* are we supposed to stop? */
619 if (WaitForSingleObject(gStopSem, 1000) == WAIT_OBJECT_0)
620 {
621 fTerminate = true;
622 break;
623 }
624 }
625 else
626 {
627 SvcDebugOut("VBoxService: error from DeviceIoControl IOCTL_VBOXGUEST_VMMREQUEST\n", 0);
628 /* sleep a bit to not eat too much CPU while retrying */
629 /* are we supposed to stop? */
630 if (WaitForSingleObject(gStopSem, 50) == WAIT_OBJECT_0)
631 {
632 fTerminate = true;
633 break;
634 }
635 }
636 }
637 }
638 } else
639 {
640 SvcDebugOut("VBoxService: error from DeviceIoControl IOCTL_VBOXGUEST_WAITEVENT\n", 0);
641 /* sleep a bit to not eat too much CPU in case the above call always fails */
642 if (WaitForSingleObject(gStopSem, 10) == WAIT_OBJECT_0)
643 {
644 fTerminate = true;
645 break;
646 }
647 }
648 } while (!fTerminate);
649
650 maskInfo.u32OrMask = 0;
651 maskInfo.u32NotMask = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
652 if (DeviceIoControl (gVBoxDriver, IOCTL_VBOXGUEST_CTL_FILTER_MASK, &maskInfo, sizeof (maskInfo), NULL, 0, &cbReturned, NULL))
653 {
654 DDCLOG(("VBoxService: DeviceIOControl(CtlMask - not) succeeded\n"));
655 }
656 else
657 {
658 SvcDebugOut2 ("VBoxService: DeviceIOControl(CtlMask) failed\n");
659 }
660
661 SvcDebugOut2("VBoxService: finished display change request thread\n");
662}
663
664/**
665 * Window procedure for our tool window
666 */
667LRESULT CALLBACK VBoxToolWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
668{
669 switch (msg)
670 {
671 case WM_CLOSE:
672 break;
673
674 case WM_DESTROY:
675 break;
676
677 default:
678 return DefWindowProc(hwnd, msg, wParam, lParam);
679 }
680 return 0;
681}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette