VirtualBox

source: vbox/trunk/src/VBox/Main/glue/NativeEventQueue.cpp@ 98103

Last change on this file since 98103 was 98103, checked in by vboxsync, 17 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.0 KB
Line 
1/* $Id: NativeEventQueue.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * MS COM / XPCOM Abstraction Layer:
4 * Main event queue class declaration
5 */
6
7/*
8 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
9 *
10 * This file is part of VirtualBox base platform packages, as
11 * available from https://www.virtualbox.org.
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation, in version 3 of the
16 * License.
17 *
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, see <https://www.gnu.org/licenses>.
25 *
26 * SPDX-License-Identifier: GPL-3.0-only
27 */
28
29#include "VBox/com/NativeEventQueue.h"
30
31#include <new> /* For bad_alloc. */
32
33#ifdef RT_OS_DARWIN
34# include <CoreFoundation/CFRunLoop.h>
35#endif
36
37#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2)
38# define USE_XPCOM_QUEUE
39#endif
40
41#include <iprt/err.h>
42#include <iprt/time.h>
43#include <iprt/thread.h>
44#include <iprt/log.h>
45#ifdef USE_XPCOM_QUEUE
46# include <errno.h>
47#endif
48
49namespace com
50{
51
52// NativeEventQueue class
53////////////////////////////////////////////////////////////////////////////////
54
55#ifndef VBOX_WITH_XPCOM
56
57# define CHECK_THREAD_RET(ret) \
58 do { \
59 AssertMsg(GetCurrentThreadId() == mThreadId, ("Must be on event queue thread!")); \
60 if (GetCurrentThreadId() != mThreadId) \
61 return ret; \
62 } while (0)
63
64/** Magic LPARAM value for the WM_USER messages that we're posting.
65 * @remarks This magic value is duplicated in
66 * vboxapi/PlatformMSCOM::interruptWaitEvents(). */
67#define EVENTQUEUE_WIN_LPARAM_MAGIC UINT32_C(0xf241b819)
68
69
70#else // VBOX_WITH_XPCOM
71
72# define CHECK_THREAD_RET(ret) \
73 do { \
74 if (!mEventQ) \
75 return ret; \
76 BOOL isOnCurrentThread = FALSE; \
77 mEventQ->IsOnCurrentThread(&isOnCurrentThread); \
78 AssertMsg(isOnCurrentThread, ("Must be on event queue thread!")); \
79 if (!isOnCurrentThread) \
80 return ret; \
81 } while (0)
82
83#endif // VBOX_WITH_XPCOM
84
85/** Pointer to the main event queue. */
86NativeEventQueue *NativeEventQueue::sMainQueue = NULL;
87
88
89#ifdef VBOX_WITH_XPCOM
90
91struct MyPLEvent : public PLEvent
92{
93 MyPLEvent(NativeEvent *e) : event(e) {}
94 NativeEvent *event;
95};
96
97/* static */
98void *PR_CALLBACK com::NativeEventQueue::plEventHandler(PLEvent *self)
99{
100 NativeEvent *ev = ((MyPLEvent *)self)->event;
101 if (ev)
102 ev->handler();
103 else
104 {
105 NativeEventQueue *eq = (NativeEventQueue *)self->owner;
106 Assert(eq);
107 eq->mInterrupted = true;
108 }
109 return NULL;
110}
111
112/* static */
113void PR_CALLBACK com::NativeEventQueue::plEventDestructor(PLEvent *self)
114{
115 NativeEvent *ev = ((MyPLEvent *)self)->event;
116 if (ev)
117 delete ev;
118 delete self;
119}
120
121#endif // VBOX_WITH_XPCOM
122
123/**
124 * Constructs an event queue for the current thread.
125 *
126 * Currently, there can be only one event queue per thread, so if an event
127 * queue for the current thread already exists, this object is simply attached
128 * to the existing event queue.
129 */
130NativeEventQueue::NativeEventQueue()
131{
132#ifndef VBOX_WITH_XPCOM
133
134 mThreadId = GetCurrentThreadId();
135 // force the system to create the message queue for the current thread
136 MSG msg;
137 PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
138
139 if (!DuplicateHandle(GetCurrentProcess(),
140 GetCurrentThread(),
141 GetCurrentProcess(),
142 &mhThread,
143 0 /*dwDesiredAccess*/,
144 FALSE /*bInheritHandle*/,
145 DUPLICATE_SAME_ACCESS))
146 mhThread = INVALID_HANDLE_VALUE;
147
148#else // VBOX_WITH_XPCOM
149
150 mEQCreated = false;
151 mInterrupted = false;
152
153 // Here we reference the global nsIEventQueueService instance and hold it
154 // until we're destroyed. This is necessary to keep NS_ShutdownXPCOM() away
155 // from calling StopAcceptingEvents() on all event queues upon destruction of
156 // nsIEventQueueService, and makes sense when, for some reason, this happens
157 // *before* we're able to send a NULL event to stop our event handler thread
158 // when doing unexpected cleanup caused indirectly by NS_ShutdownXPCOM()
159 // that is performing a global cleanup of everything. A good example of such
160 // situation is when NS_ShutdownXPCOM() is called while the VirtualBox component
161 // is still alive (because it is still referenced): eventually, it results in
162 // a VirtualBox::uninit() call from where it is already not possible to post
163 // NULL to the event thread (because it stopped accepting events).
164
165 nsresult rc = NS_GetEventQueueService(getter_AddRefs(mEventQService));
166
167 if (NS_SUCCEEDED(rc))
168 {
169 rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
170 getter_AddRefs(mEventQ));
171 if (rc == NS_ERROR_NOT_AVAILABLE)
172 {
173 rc = mEventQService->CreateThreadEventQueue();
174 if (NS_SUCCEEDED(rc))
175 {
176 mEQCreated = true;
177 rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
178 getter_AddRefs(mEventQ));
179 }
180 }
181 }
182 AssertComRC(rc);
183
184#endif // VBOX_WITH_XPCOM
185}
186
187NativeEventQueue::~NativeEventQueue()
188{
189#ifndef VBOX_WITH_XPCOM
190 if (mhThread != INVALID_HANDLE_VALUE)
191 {
192 CloseHandle(mhThread);
193 mhThread = INVALID_HANDLE_VALUE;
194 }
195#else // VBOX_WITH_XPCOM
196 // process all pending events before destruction
197 if (mEventQ)
198 {
199 if (mEQCreated)
200 {
201 mEventQ->StopAcceptingEvents();
202 mEventQ->ProcessPendingEvents();
203 mEventQService->DestroyThreadEventQueue();
204 }
205 mEventQ = nsnull;
206 mEventQService = nsnull;
207 }
208#endif // VBOX_WITH_XPCOM
209}
210
211/**
212 * Initializes the main event queue instance.
213 * @returns VBox status code.
214 *
215 * @remarks If you're using the rest of the COM/XPCOM glue library,
216 * com::Initialize() will take care of initializing and uninitializing
217 * the NativeEventQueue class. If you don't call com::Initialize, you must
218 * make sure to call this method on the same thread that did the
219 * XPCOM initialization or we'll end up using the wrong main queue.
220 */
221/* static */
222int NativeEventQueue::init()
223{
224 Assert(sMainQueue == NULL);
225 Assert(RTThreadIsMain(RTThreadSelf()));
226
227 try
228 {
229 sMainQueue = new NativeEventQueue();
230 AssertPtr(sMainQueue);
231#ifdef VBOX_WITH_XPCOM
232 /* Check that it actually is the main event queue, i.e. that
233 we're called on the right thread. */
234 nsCOMPtr<nsIEventQueue> q;
235 nsresult rv = NS_GetMainEventQ(getter_AddRefs(q));
236 AssertComRCReturn(rv, VERR_INVALID_POINTER);
237 Assert(q == sMainQueue->mEventQ);
238
239 /* Check that it's a native queue. */
240 PRBool fIsNative = PR_FALSE;
241 rv = sMainQueue->mEventQ->IsQueueNative(&fIsNative);
242 Assert(NS_SUCCEEDED(rv) && fIsNative);
243#endif // VBOX_WITH_XPCOM
244 }
245 catch (std::bad_alloc &ba)
246 {
247 NOREF(ba);
248 return VERR_NO_MEMORY;
249 }
250
251 return VINF_SUCCESS;
252}
253
254/**
255 * Uninitialize the global resources (i.e. the main event queue instance).
256 * @returns VINF_SUCCESS
257 */
258/* static */
259int NativeEventQueue::uninit()
260{
261 if (sMainQueue)
262 {
263 /* Must process all events to make sure that no NULL event is left
264 * after this point. It would need to modify the state of sMainQueue. */
265#ifdef RT_OS_DARWIN /* Do not process the native runloop, the toolkit may not be ready for it. */
266 sMainQueue->mEventQ->ProcessPendingEvents();
267#else
268 sMainQueue->processEventQueue(0);
269#endif
270 delete sMainQueue;
271 sMainQueue = NULL;
272 }
273 return VINF_SUCCESS;
274}
275
276/**
277 * Get main event queue instance.
278 *
279 * Depends on init() being called first.
280 */
281/* static */
282NativeEventQueue* NativeEventQueue::getMainEventQueue()
283{
284 return sMainQueue;
285}
286
287#ifdef VBOX_WITH_XPCOM
288# ifdef RT_OS_DARWIN
289/**
290 * Wait for events and process them (Darwin).
291 *
292 * @retval VINF_SUCCESS
293 * @retval VERR_TIMEOUT
294 * @retval VERR_INTERRUPTED
295 *
296 * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
297 */
298static int waitForEventsOnDarwin(RTMSINTERVAL cMsTimeout)
299{
300 /*
301 * Wait for the requested time, if we get a hit we do a poll to process
302 * any other pending messages.
303 *
304 * Note! About 1.0e10: According to the sources anything above 3.1556952e+9
305 * means indefinite wait and 1.0e10 is what CFRunLoopRun() uses.
306 */
307 CFTimeInterval rdTimeout = cMsTimeout == RT_INDEFINITE_WAIT ? 1e10 : (double)cMsTimeout / 1000;
308 OSStatus orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, rdTimeout, true /*returnAfterSourceHandled*/);
309 if (orc == kCFRunLoopRunHandledSource)
310 {
311 OSStatus orc2 = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/);
312 if ( orc2 == kCFRunLoopRunStopped
313 || orc2 == kCFRunLoopRunFinished)
314 orc = orc2;
315 }
316 if ( orc == 0 /*???*/
317 || orc == kCFRunLoopRunHandledSource)
318 return VINF_SUCCESS;
319 if ( orc == kCFRunLoopRunStopped
320 || orc == kCFRunLoopRunFinished)
321 return VERR_INTERRUPTED;
322 AssertMsg(orc == kCFRunLoopRunTimedOut, ("Unexpected status code from CFRunLoopRunInMode: %#x", orc));
323 return VERR_TIMEOUT;
324}
325# else // !RT_OS_DARWIN
326
327/**
328 * Wait for events (generic XPCOM).
329 *
330 * @retval VINF_SUCCESS
331 * @retval VERR_TIMEOUT
332 * @retval VINF_INTERRUPTED
333 * @retval VERR_INTERNAL_ERROR_4
334 *
335 * @param pQueue The queue to wait on.
336 * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
337 */
338static int waitForEventsOnXPCOM(nsIEventQueue *pQueue, RTMSINTERVAL cMsTimeout)
339{
340 int fd = pQueue->GetEventQueueSelectFD();
341 fd_set fdsetR;
342 FD_ZERO(&fdsetR);
343 FD_SET(fd, &fdsetR);
344
345 fd_set fdsetE = fdsetR;
346
347 struct timeval tv = {0,0};
348 struct timeval *ptv;
349 if (cMsTimeout == RT_INDEFINITE_WAIT)
350 ptv = NULL;
351 else
352 {
353 tv.tv_sec = cMsTimeout / 1000;
354 tv.tv_usec = (cMsTimeout % 1000) * 1000;
355 ptv = &tv;
356 }
357
358 int rc = select(fd + 1, &fdsetR, NULL, &fdsetE, ptv);
359 if (rc > 0)
360 rc = VINF_SUCCESS;
361 else if (rc == 0)
362 rc = VERR_TIMEOUT;
363 else if (errno == EINTR)
364 rc = VINF_INTERRUPTED;
365 else
366 {
367 static uint32_t s_ErrorCount = 0;
368 if (s_ErrorCount < 500)
369 {
370 LogRel(("waitForEventsOnXPCOM rc=%d errno=%d\n", rc, errno));
371 ++s_ErrorCount;
372 }
373
374 AssertMsgFailed(("rc=%d errno=%d\n", rc, errno));
375 rc = VERR_INTERNAL_ERROR_4;
376 }
377 return rc;
378}
379
380# endif // !RT_OS_DARWIN
381#endif // VBOX_WITH_XPCOM
382
383#ifndef VBOX_WITH_XPCOM
384
385/**
386 * Dispatch a message on Windows.
387 *
388 * This will pick out our events and handle them specially.
389 *
390 * @returns @a rc or VERR_INTERRUPTED (WM_QUIT or NULL msg).
391 * @param pMsg The message to dispatch.
392 * @param rc The current status code.
393 */
394/*static*/
395int NativeEventQueue::dispatchMessageOnWindows(MSG const *pMsg, int rc)
396{
397 /*
398 * Check for and dispatch our events.
399 */
400 if ( pMsg->hwnd == NULL
401 && pMsg->message == WM_USER)
402 {
403 if (pMsg->lParam == EVENTQUEUE_WIN_LPARAM_MAGIC)
404 {
405 NativeEvent *pEvent = (NativeEvent *)pMsg->wParam;
406 if (pEvent)
407 {
408 pEvent->handler();
409 delete pEvent;
410 }
411 else
412 rc = VERR_INTERRUPTED;
413 return rc;
414 }
415 AssertMsgFailed(("lParam=%p wParam=%p\n", pMsg->lParam, pMsg->wParam));
416 }
417
418 /*
419 * Check for the quit message and dispatch the message the normal way.
420 */
421 if (pMsg->message == WM_QUIT)
422 rc = VERR_INTERRUPTED;
423 TranslateMessage(pMsg);
424 DispatchMessage(pMsg);
425
426 return rc;
427}
428
429
430/**
431 * Process pending events (Windows).
432 *
433 * @retval VINF_SUCCESS
434 * @retval VERR_TIMEOUT
435 * @retval VERR_INTERRUPTED.
436 */
437static int processPendingEvents(void)
438{
439 int rc = VERR_TIMEOUT;
440 MSG Msg;
441 if (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE))
442 {
443 rc = VINF_SUCCESS;
444 do
445 rc = NativeEventQueue::dispatchMessageOnWindows(&Msg, rc);
446 while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE));
447 }
448 return rc;
449}
450
451#else // VBOX_WITH_XPCOM
452
453/**
454 * Process pending XPCOM events.
455 * @param pQueue The queue to process events on.
456 * @retval VINF_SUCCESS
457 * @retval VERR_TIMEOUT
458 * @retval VERR_INTERRUPTED (darwin only)
459 * @retval VERR_INTERNAL_ERROR_2
460 */
461static int processPendingEvents(nsIEventQueue *pQueue)
462{
463 /* ProcessPendingEvents doesn't report back what it did, so check here. */
464 PRBool fHasEvents = PR_FALSE;
465 nsresult hr = pQueue->PendingEvents(&fHasEvents);
466 if (NS_FAILED(hr))
467 return VERR_INTERNAL_ERROR_2;
468
469 /* Process pending events. */
470 int rc = VINF_SUCCESS;
471 if (fHasEvents)
472 pQueue->ProcessPendingEvents();
473 else
474 rc = VERR_TIMEOUT;
475
476# ifdef RT_OS_DARWIN
477 /* Process pending native events. */
478 int rc2 = waitForEventsOnDarwin(0);
479 if (rc == VERR_TIMEOUT || rc2 == VERR_INTERRUPTED)
480 rc = rc2;
481# endif
482
483 return rc;
484}
485
486#endif // VBOX_WITH_XPCOM
487
488/**
489 * Process events pending on this event queue, and wait up to given timeout, if
490 * nothing is available.
491 *
492 * Must be called on same thread this event queue was created on.
493 *
494 * @param cMsTimeout The timeout specified as milliseconds. Use
495 * RT_INDEFINITE_WAIT to wait till an event is posted on the
496 * queue.
497 *
498 * @returns VBox status code
499 * @retval VINF_SUCCESS if one or more messages was processed.
500 * @retval VERR_TIMEOUT if cMsTimeout expired.
501 * @retval VERR_INVALID_CONTEXT if called on the wrong thread.
502 * @retval VERR_INTERRUPTED if interruptEventQueueProcessing was called.
503 * On Windows will also be returned when WM_QUIT is encountered.
504 * On Darwin this may also be returned when the native queue is
505 * stopped or destroyed/finished.
506 * @retval VINF_INTERRUPTED if the native system call was interrupted by a
507 * an asynchronous event delivery (signal) or just felt like returning
508 * out of bounds. On darwin it will also be returned if the queue is
509 * stopped.
510 *
511 * @note On darwin this function will not return when the thread receives a
512 * signal, it will just resume the wait.
513 */
514int NativeEventQueue::processEventQueue(RTMSINTERVAL cMsTimeout)
515{
516 int rc;
517 CHECK_THREAD_RET(VERR_INVALID_CONTEXT);
518
519#ifdef VBOX_WITH_XPCOM
520 /*
521 * Process pending events, if none are available and we're not in a
522 * poll call, wait for some to appear. (We have to be a little bit
523 * careful after waiting for the events since Darwin will process
524 * them as part of the wait, while the XPCOM case will not.)
525 *
526 * Note! Unfortunately, WaitForEvent isn't interruptible with Ctrl-C,
527 * while select() is. So we cannot use it for indefinite waits.
528 */
529 rc = processPendingEvents(mEventQ);
530 if ( rc == VERR_TIMEOUT
531 && cMsTimeout > 0)
532 {
533# ifdef RT_OS_DARWIN
534 /** @todo check how Ctrl-C works on Darwin.
535 * Update: It doesn't work. MACH_RCV_INTERRUPT could perhaps be returned
536 * to __CFRunLoopServiceMachPort, but neither it nor __CFRunLoopRun
537 * has any way of expressing it via their return values. So, if
538 * Ctrl-C handling is important, signal needs to be handled on
539 * a different thread or something. */
540 rc = waitForEventsOnDarwin(cMsTimeout);
541# else // !RT_OS_DARWIN
542 rc = waitForEventsOnXPCOM(mEventQ, cMsTimeout);
543# endif // !RT_OS_DARWIN
544 if ( RT_SUCCESS(rc)
545 || rc == VERR_TIMEOUT)
546 {
547 int rc2 = processPendingEvents(mEventQ);
548 /* If the wait was successful don't fail the whole operation. */
549 if (RT_FAILURE(rc) && RT_FAILURE(rc2))
550 rc = rc2;
551 }
552 }
553
554 if ( ( RT_SUCCESS(rc)
555 || rc == VERR_INTERRUPTED
556 || rc == VERR_TIMEOUT)
557 && mInterrupted)
558 {
559 mInterrupted = false;
560 rc = VERR_INTERRUPTED;
561 }
562
563#else // !VBOX_WITH_XPCOM
564 if (cMsTimeout == RT_INDEFINITE_WAIT)
565 {
566 BOOL fRet = 0; /* Shut up MSC */
567 MSG Msg;
568 rc = VINF_SUCCESS;
569 while ( rc != VERR_INTERRUPTED
570 && (fRet = GetMessage(&Msg, NULL /*hWnd*/, WM_USER, WM_USER))
571 && fRet != -1)
572 rc = NativeEventQueue::dispatchMessageOnWindows(&Msg, rc);
573 if (fRet == 0)
574 rc = VERR_INTERRUPTED;
575 else if (fRet == -1)
576 rc = RTErrConvertFromWin32(GetLastError());
577 }
578 else
579 {
580 rc = processPendingEvents();
581 if ( rc == VERR_TIMEOUT
582 && cMsTimeout != 0)
583 {
584 DWORD rcW = MsgWaitForMultipleObjects(1,
585 &mhThread,
586 TRUE /*fWaitAll*/,
587 cMsTimeout,
588 QS_ALLINPUT);
589 AssertMsgReturn(rcW == WAIT_TIMEOUT || rcW == WAIT_OBJECT_0,
590 ("%d\n", rcW),
591 VERR_INTERNAL_ERROR_4);
592 rc = processPendingEvents();
593 }
594 }
595#endif // !VBOX_WITH_XPCOM
596
597 Assert(rc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT);
598 return rc;
599}
600
601/**
602 * Interrupt thread waiting on event queue processing.
603 *
604 * Can be called on any thread.
605 *
606 * @returns VBox status code.
607 */
608int NativeEventQueue::interruptEventQueueProcessing()
609{
610 /* Send a NULL event. This event will be picked up and handled specially
611 * both for XPCOM and Windows. It is the responsibility of the caller to
612 * take care of not running the loop again in a way which will hang. */
613 postEvent(NULL);
614 return VINF_SUCCESS;
615}
616
617/**
618 * Posts an event to this event loop asynchronously.
619 *
620 * @param pEvent the event to post, must be allocated using |new|
621 * @return @c TRUE if successful and false otherwise
622 */
623BOOL NativeEventQueue::postEvent(NativeEvent *pEvent)
624{
625#ifndef VBOX_WITH_XPCOM
626 /* Note! The event == NULL case is duplicated in vboxapi/PlatformMSCOM::interruptWaitEvents(). */
627 BOOL fRc = PostThreadMessage(mThreadId, WM_USER, (WPARAM)pEvent, EVENTQUEUE_WIN_LPARAM_MAGIC);
628 if (!fRc)
629 {
630 static int s_cBitchedAboutFullNativeEventQueue = 0;
631 if ( GetLastError() == ERROR_NOT_ENOUGH_QUOTA
632 && s_cBitchedAboutFullNativeEventQueue < 10)
633 LogRel(("Warning: Asynchronous event queue (%p, thread %RI32) full, event (%p) not delivered (%d/10)\n",
634 this, mThreadId, pEvent, ++s_cBitchedAboutFullNativeEventQueue));
635 else
636 AssertFailed();
637 }
638 return fRc;
639#else // VBOX_WITH_XPCOM
640 if (!mEventQ)
641 return FALSE;
642
643 try
644 {
645 MyPLEvent *pMyEvent = new MyPLEvent(pEvent);
646 mEventQ->InitEvent(pMyEvent, this, com::NativeEventQueue::plEventHandler,
647 com::NativeEventQueue::plEventDestructor);
648 HRESULT rc = mEventQ->PostEvent(pMyEvent);
649 return NS_SUCCEEDED(rc);
650 }
651 catch (std::bad_alloc &ba)
652 {
653 AssertMsgFailed(("Out of memory while allocating memory for event=%p: %s\n",
654 pEvent, ba.what()));
655 }
656
657 return FALSE;
658#endif // VBOX_WITH_XPCOM
659}
660
661/**
662 * Get select()'able selector for this event queue.
663 * This will return -1 on platforms and queue variants not supporting such
664 * functionality.
665 */
666int NativeEventQueue::getSelectFD()
667{
668#ifdef VBOX_WITH_XPCOM
669 return mEventQ->GetEventQueueSelectFD();
670#else
671 return -1;
672#endif
673}
674
675}
676/* namespace com */
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use