VirtualBox

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

Last change on this file since 95353 was 95186, checked in by vboxsync, 2 years ago

Main/glue: Updated NativeEventQueue::processEventQueue with more details on darwin and Ctrl-C. [scm] bugref:9898

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