VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxDnDDataObject.cpp@ 51556

Last change on this file since 51556 was 51476, checked in by vboxsync, 11 years ago

DnD: API overhaul; now using IDnDTarget + IDnDSource. Renamed DragAndDrop* enumerations to DnD*. Also rewrote some internal code.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.0 KB
Line 
1/* $Id: VBoxDnDDataObject.cpp 51476 2014-05-30 14:58:02Z vboxsync $ */
2/** @file
3 * VBoxDnDDataObject.cpp - IDataObject implementation.
4 */
5
6/*
7 * Copyright (C) 2013-2014 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#include <windows.h>
18#include <new> /* For bad_alloc. */
19#include <shlobj.h>
20
21#include <iprt/path.h>
22#include <iprt/semaphore.h>
23#include <iprt/uri.h>
24
25#ifdef LOG_GROUP
26# undef LOG_GROUP
27#endif
28#define LOG_GROUP LOG_GROUP_GUEST_DND
29#include <VBox/log.h>
30
31#include "VBoxTray.h"
32#include "VBoxHelpers.h"
33#include "VBoxDnD.h"
34
35#ifdef DEBUG
36 /* Enable the following line to get much more debug output about
37 * (un)known clipboard formats. */
38//# define VBOX_DND_DEBUG_FORMATS
39#endif
40
41/** @todo Implement IDataObjectAsyncCapability interface? */
42
43VBoxDnDDataObject::VBoxDnDDataObject(FORMATETC *pFormatEtc,
44 STGMEDIUM *pStgMed, ULONG cFormats)
45 : mStatus(Uninitialized),
46 mRefCount(1),
47 mcFormats(0),
48 mpvData(NULL),
49 mcbData(0)
50{
51 HRESULT hr;
52
53 /* Make sure that there's enough room for our fixed formats. */
54 ULONG cAllFormats = cFormats + 1;
55
56 try
57 {
58 mpFormatEtc = new FORMATETC[cAllFormats];
59 RT_BZERO(mpFormatEtc, sizeof(FORMATETC) * cAllFormats);
60 mpStgMedium = new STGMEDIUM[cAllFormats];
61 RT_BZERO(mpStgMedium, sizeof(STGMEDIUM) * cAllFormats);
62
63 if ( pFormatEtc
64 && pStgMed)
65 {
66 for (ULONG i = 0; i < cFormats; i++)
67 {
68 LogFlowFunc(("Format %RU32: cfFormat=%RI16, tyMed=%RU32, dwAspect=%RU32\n",
69 i, pFormatEtc[i].cfFormat, pFormatEtc[i].tymed, pFormatEtc[i].dwAspect));
70 mpFormatEtc[i] = pFormatEtc[i];
71 mpStgMedium[i] = pStgMed[i];
72 }
73 }
74
75 hr = S_OK;
76 }
77 catch (std::bad_alloc &)
78 {
79 hr = E_OUTOFMEMORY;
80 }
81
82 if (SUCCEEDED(hr))
83 {
84 int rc2 = RTSemEventCreate(&mSemEvent);
85 AssertRC(rc2);
86
87 /* Most commonly used format. */
88 RegisterFormat(&mpFormatEtc[cFormats], CF_HDROP);
89 mpStgMedium[cFormats++].tymed = TYMED_HGLOBAL;
90#if 0
91 /* IStream. */
92 RegisterFormat(&mpFormatEtc[cFormats++],
93 RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
94 RegisterFormat(&mpFormatEtc[cFormats++],
95 RegisterClipboardFormat(CFSTR_FILECONTENTS),
96 TYMED_ISTREAM, 0 /* lIndex */);
97
98 /* Required for e.g. Windows Media Player. */
99 RegisterFormat(&mpFormatEtc[cFormats++],
100 RegisterClipboardFormat(CFSTR_FILENAME));
101 RegisterFormat(&mpFormatEtc[cFormats++],
102 RegisterClipboardFormat(CFSTR_FILENAMEW));
103 RegisterFormat(&mpFormatEtc[cFormats++],
104 RegisterClipboardFormat(CFSTR_SHELLIDLIST));
105 RegisterFormat(&mpFormatEtc[cFormats++],
106 RegisterClipboardFormat(CFSTR_SHELLIDLISTOFFSET));
107#endif
108 mcFormats = cFormats;
109 mStatus = Initialized;
110 }
111
112 LogFlowFunc(("cFormats=%RU32, hr=%Rhrc\n", cFormats, hr));
113}
114
115VBoxDnDDataObject::~VBoxDnDDataObject(void)
116{
117 if (mpFormatEtc)
118 delete[] mpFormatEtc;
119
120 if (mpStgMedium)
121 delete[] mpStgMedium;
122
123 if (mpvData)
124 RTMemFree(mpvData);
125
126 LogFlowFunc(("mRefCount=%RI32\n", mRefCount));
127}
128
129/* static */
130int VBoxDnDDataObject::CreateDataObject(FORMATETC *pFormatEtc, STGMEDIUM *pStgMeds,
131 ULONG cFormats, IDataObject **ppDataObject)
132{
133 AssertPtrReturn(pFormatEtc, VERR_INVALID_POINTER);
134 AssertPtrReturn(pStgMeds, VERR_INVALID_POINTER);
135 AssertPtrReturn(ppDataObject, VERR_INVALID_POINTER);
136
137 int rc;
138 try
139 {
140 *ppDataObject = new VBoxDnDDataObject(pFormatEtc, pStgMeds, cFormats);
141 rc = VINF_SUCCESS;
142 }
143 catch(std::bad_alloc &)
144 {
145 rc = VERR_NO_MEMORY;
146 }
147
148 return rc;
149}
150
151/*
152 * IUnknown methods.
153 */
154
155STDMETHODIMP_(ULONG) VBoxDnDDataObject::AddRef(void)
156{
157 return InterlockedIncrement(&mRefCount);
158}
159
160STDMETHODIMP_(ULONG) VBoxDnDDataObject::Release(void)
161{
162 LONG lCount = InterlockedDecrement(&mRefCount);
163 if (lCount == 0)
164 {
165 delete this;
166 return 0;
167 }
168
169 return lCount;
170}
171
172STDMETHODIMP VBoxDnDDataObject::QueryInterface(REFIID iid, void **ppvObject)
173{
174 AssertPtrReturn(ppvObject, E_INVALIDARG);
175
176 if ( iid == IID_IDataObject
177 || iid == IID_IUnknown)
178 {
179 AddRef();
180 *ppvObject = this;
181 return S_OK;
182 }
183
184 *ppvObject = 0;
185 return E_NOINTERFACE;
186}
187
188/**
189 * Retrieves the data stored in this object and store the result in
190 * pMedium.
191 *
192 * @return IPRT status code.
193 * @return HRESULT
194 * @param pFormatEtc
195 * @param pMedium
196 */
197STDMETHODIMP VBoxDnDDataObject::GetData(FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
198{
199 AssertPtrReturn(pFormatEtc, DV_E_FORMATETC);
200 AssertPtrReturn(pMedium, DV_E_FORMATETC);
201
202 ULONG lIndex;
203 if (!LookupFormatEtc(pFormatEtc, &lIndex)) /* Format supported? */
204 return DV_E_FORMATETC;
205 if (lIndex >= mcFormats) /* Paranoia. */
206 return DV_E_FORMATETC;
207
208 FORMATETC *pThisFormat = &mpFormatEtc[lIndex];
209 AssertPtr(pThisFormat);
210
211 STGMEDIUM *pThisMedium = &mpStgMedium[lIndex];
212 AssertPtr(pThisMedium);
213
214 LogFlowFunc(("Using pThisFormat=%p, pThisMedium=%p\n", pThisFormat, pThisMedium));
215
216 HRESULT hr = DV_E_FORMATETC;
217
218 LogFlowFunc(("mStatus=%ld\n", mStatus));
219 if (mStatus == Dropping)
220 {
221 LogFlowFunc(("Waiting for event ...\n"));
222 int rc2 = RTSemEventWait(mSemEvent, RT_INDEFINITE_WAIT);
223 LogFlowFunc(("rc=%Rrc, mStatus=%ld\n", rc2, mStatus));
224 }
225
226 if (mStatus == Dropped)
227 {
228 LogFlowFunc(("cfFormat=%RI16, sFormat=%s, tyMed=%RU32, dwAspect=%RU32\n",
229 pThisFormat->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(pFormatEtc->cfFormat),
230 pThisFormat->tymed, pThisFormat->dwAspect));
231 LogFlowFunc(("Got strFormat=%s, pvData=%p, cbData=%RU32\n",
232 mstrFormat.c_str(), mpvData, mcbData));
233
234 if (mstrFormat.equalsIgnoreCase("text/uri-list"))
235 {
236 RTCList<RTCString> lstFilesURI = RTCString((char*)mpvData, mcbData).split("\r\n");
237 RTCList<RTCString> lstFiles;
238 for (size_t i = 0; i < lstFilesURI.size(); i++)
239 {
240 /* Extract path from URI. */
241 char *pszPath = RTUriPath(lstFilesURI.at(i).c_str());
242 if ( pszPath
243 && strlen(pszPath) > 1)
244 {
245 pszPath++; /** @todo Skip first '/' (part of URI). Correct? */
246 pszPath = RTPathChangeToDosSlashes(pszPath, false /* fForce */);
247 lstFiles.append(pszPath);
248 }
249 }
250#ifdef DEBUG
251 LogFlowFunc(("Files (%zu)\n", lstFiles.size()));
252 for (size_t i = 0; i < lstFiles.size(); i++)
253 LogFlowFunc(("\tFile: %s\n", lstFiles.at(i).c_str()));
254#endif
255
256#if 0
257 if ( (pFormatEtc->tymed & TYMED_ISTREAM)
258 && (pFormatEtc->dwAspect == DVASPECT_CONTENT)
259 && (pFormatEtc->cfFormat == CF_FILECONTENTS))
260 {
261
262 }
263 else if ( (pFormatEtc->tymed & TYMED_HGLOBAL)
264 && (pFormatEtc->dwAspect == DVASPECT_CONTENT)
265 && (pFormatEtc->cfFormat == CF_FILEDESCRIPTOR))
266 {
267
268 }
269 else if ( (pFormatEtc->tymed & TYMED_HGLOBAL)
270 && (pFormatEtc->cfFormat == CF_PREFERREDDROPEFFECT))
271 {
272 HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, sizeof(DWORD));
273 DWORD *pdwEffect = (DWORD *)GlobalLock(hData);
274 AssertPtr(pdwEffect);
275 *pdwEffect = DROPEFFECT_COPY;
276 GlobalUnlock(hData);
277
278 pMedium->hGlobal = hData;
279 pMedium->tymed = TYMED_HGLOBAL;
280 }
281 else
282#endif
283 if ( (pFormatEtc->tymed & TYMED_HGLOBAL)
284 && (pFormatEtc->dwAspect == DVASPECT_CONTENT)
285 && (pFormatEtc->cfFormat == CF_TEXT))
286 {
287 pMedium->hGlobal = GlobalAlloc(GHND, mcbData + 1);
288 if (pMedium->hGlobal)
289 {
290 char *pcDst = (char *)GlobalLock(pMedium->hGlobal);
291 memcpy(pcDst, mpvData, mcbData);
292 pcDst[mcbData] = '\0';
293 GlobalUnlock(pMedium->hGlobal);
294
295 hr = S_OK;
296 }
297 }
298 else if ( (pFormatEtc->tymed & TYMED_HGLOBAL)
299 && (pFormatEtc->dwAspect == DVASPECT_CONTENT)
300 && (pFormatEtc->cfFormat == CF_HDROP))
301 {
302 int rc = VINF_SUCCESS;
303
304 size_t cchFiles = 0; /* Number of ASCII characters. */
305 for (size_t i = 0; i < lstFiles.size(); i++)
306 {
307 cchFiles += strlen(lstFiles.at(i).c_str());
308 cchFiles += 1; /* Terminating '\0'. */
309 }
310
311 size_t cbBuf = sizeof(DROPFILES) + ((cchFiles + 1) * sizeof(RTUTF16));
312 DROPFILES *pBuf = (DROPFILES *)RTMemAllocZ(cbBuf);
313 if (pBuf)
314 {
315 pBuf->pFiles = sizeof(DROPFILES);
316 pBuf->fWide = 1; /* We use unicode. Always. */
317
318 uint8_t *pCurFile = (uint8_t *)pBuf + pBuf->pFiles;
319 AssertPtr(pCurFile);
320
321 for (size_t i = 0; i < lstFiles.size() && RT_SUCCESS(rc); i++)
322 {
323 size_t cchCurFile;
324 PRTUTF16 pwszFile;
325 rc = RTStrToUtf16(lstFiles.at(i).c_str(), &pwszFile);
326 if (RT_SUCCESS(rc))
327 {
328 cchCurFile = RTUtf16Len(pwszFile);
329 Assert(cchCurFile);
330 memcpy(pCurFile, pwszFile, cchCurFile * sizeof(RTUTF16));
331 RTUtf16Free(pwszFile);
332 }
333 else
334 break;
335
336 pCurFile += cchCurFile * sizeof(RTUTF16);
337
338 /* Terminate current file name. */
339 *pCurFile = L'\0';
340 pCurFile += sizeof(RTUTF16);
341 }
342
343 if (RT_SUCCESS(rc))
344 {
345 *pCurFile = L'\0'; /* Final list terminator. */
346
347 pMedium->tymed = TYMED_HGLOBAL;
348 pMedium->pUnkForRelease = NULL;
349 pMedium->hGlobal = GlobalAlloc( GMEM_ZEROINIT
350 | GMEM_MOVEABLE
351 | GMEM_DDESHARE, cbBuf);
352 if (pMedium->hGlobal)
353 {
354 LPVOID pMem = GlobalLock(pMedium->hGlobal);
355 if (pMem)
356 {
357 memcpy(pMem, pBuf, cbBuf);
358 GlobalUnlock(pMedium->hGlobal);
359
360 hr = S_OK;
361 }
362 }
363 }
364
365 RTMemFree(pBuf);
366 }
367 else
368 rc = VERR_NO_MEMORY;
369
370 if (RT_FAILURE(rc))
371 hr = DV_E_FORMATETC;
372 }
373 }
374 }
375
376 if (FAILED(hr))
377 {
378 LogFlowFunc(("Copying medium ...\n"));
379 switch (pThisMedium->tymed)
380 {
381
382 case TYMED_HGLOBAL:
383 pMedium->hGlobal = (HGLOBAL)OleDuplicateData(pThisMedium->hGlobal,
384 pThisFormat->cfFormat, NULL);
385 break;
386
387 default:
388 break;
389 }
390
391 pMedium->tymed = pThisFormat->tymed;
392 pMedium->pUnkForRelease = NULL;
393 }
394
395 LogFlowFunc(("hr=%Rhrc\n", hr));
396 return hr;
397}
398
399/**
400 * Only required for IStream / IStorage interfaces.
401 *
402 * @return IPRT status code.
403 * @return HRESULT
404 * @param pFormatEtc
405 * @param pMedium
406 */
407STDMETHODIMP VBoxDnDDataObject::GetDataHere(FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
408{
409 LogFlowFunc(("\n"));
410 return DATA_E_FORMATETC;
411}
412
413/**
414 * Query if this objects supports a specific format.
415 *
416 * @return IPRT status code.
417 * @return HRESULT
418 * @param pFormatEtc
419 */
420STDMETHODIMP VBoxDnDDataObject::QueryGetData(FORMATETC *pFormatEtc)
421{
422 LogFlowFunc(("\n"));
423 return (LookupFormatEtc(pFormatEtc, NULL /* puIndex */)) ? S_OK : DV_E_FORMATETC;
424}
425
426STDMETHODIMP VBoxDnDDataObject::GetCanonicalFormatEtc(FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut)
427{
428 LogFlowFunc(("\n"));
429
430 /* Set this to NULL in any case. */
431 pFormatEtcOut->ptd = NULL;
432 return E_NOTIMPL;
433}
434
435STDMETHODIMP VBoxDnDDataObject::SetData(FORMATETC *pFormatEtc, STGMEDIUM *pMedium, BOOL fRelease)
436{
437 return E_NOTIMPL;
438}
439
440STDMETHODIMP VBoxDnDDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
441{
442 LogFlowFunc(("dwDirection=%RI32, mcFormats=%RI32, mpFormatEtc=%p\n",
443 dwDirection, mcFormats, mpFormatEtc));
444
445 HRESULT hr;
446 if (dwDirection == DATADIR_GET)
447 {
448 hr = VBoxDnDEnumFormatEtc::CreateEnumFormatEtc(mcFormats, mpFormatEtc, ppEnumFormatEtc);
449 }
450 else
451 hr = E_NOTIMPL;
452
453 LogFlowFunc(("hr=%Rhrc\n", hr));
454 return hr;
455}
456
457STDMETHODIMP VBoxDnDDataObject::DAdvise(FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection)
458{
459 return OLE_E_ADVISENOTSUPPORTED;
460}
461
462STDMETHODIMP VBoxDnDDataObject::DUnadvise(DWORD dwConnection)
463{
464 return OLE_E_ADVISENOTSUPPORTED;
465}
466
467STDMETHODIMP VBoxDnDDataObject::EnumDAdvise(IEnumSTATDATA **ppEnumAdvise)
468{
469 return OLE_E_ADVISENOTSUPPORTED;
470}
471
472/*
473 * Own stuff.
474 */
475
476int VBoxDnDDataObject::Abort(void)
477{
478 LogFlowFunc(("Aborting ...\n"));
479 mStatus = Aborted;
480 return RTSemEventSignal(mSemEvent);
481}
482
483/* static */
484const char* VBoxDnDDataObject::ClipboardFormatToString(CLIPFORMAT fmt)
485{
486#if 0
487 char szFormat[128];
488 if (GetClipboardFormatName(fmt, szFormat, sizeof(szFormat)))
489 LogFlowFunc(("wFormat=%RI16, szName=%s\n", fmt, szFormat));
490#endif
491
492 switch (fmt)
493 {
494
495 case 1:
496 return "CF_TEXT";
497 case 2:
498 return "CF_BITMAP";
499 case 3:
500 return "CF_METAFILEPICT";
501 case 4:
502 return "CF_SYLK";
503 case 5:
504 return "CF_DIF";
505 case 6:
506 return "CF_TIFF";
507 case 7:
508 return "CF_OEMTEXT";
509 case 8:
510 return "CF_DIB";
511 case 9:
512 return "CF_PALETTE";
513 case 10:
514 return "CF_PENDATA";
515 case 11:
516 return "CF_RIFF";
517 case 12:
518 return "CF_WAVE";
519 case 13:
520 return "CF_UNICODETEXT";
521 case 14:
522 return "CF_ENHMETAFILE";
523 case 15:
524 return "CF_HDROP";
525 case 16:
526 return "CF_LOCALE";
527 case 17:
528 return "CF_DIBV5";
529 case 18:
530 return "CF_MAX";
531 case 49158:
532 return "FileName";
533 case 49159:
534 return "FileNameW";
535 case 49161:
536 return "DATAOBJECT";
537 case 49171:
538 return "Ole Private Data";
539 case 49314:
540 return "Shell Object Offsets";
541 case 49316:
542 return "File Contents";
543 case 49317:
544 return "File Group Descriptor";
545 case 49323:
546 return "Preferred Drop Effect";
547 case 49380:
548 return "Shell Object Offsets";
549 case 49382:
550 return "FileContents";
551 case 49383:
552 return "FileGroupDescriptor";
553 case 49389:
554 return "Preferred DropEffect";
555 case 49268:
556 return "Shell IDList Array";
557 case 49619:
558 return "RenPrivateFileAttachments";
559 default:
560 break;
561 }
562
563 return "unknown";
564}
565
566bool VBoxDnDDataObject::LookupFormatEtc(FORMATETC *pFormatEtc, ULONG *puIndex)
567{
568 AssertReturn(pFormatEtc, false);
569 /* puIndex is optional. */
570
571 for (ULONG i = 0; i < mcFormats; i++)
572 {
573 if( (pFormatEtc->tymed & mpFormatEtc[i].tymed)
574 && pFormatEtc->cfFormat == mpFormatEtc[i].cfFormat
575 && pFormatEtc->dwAspect == mpFormatEtc[i].dwAspect)
576 {
577 LogFlowFunc(("Format found: tyMed=%RI32, cfFormat=%RI16, sFormats=%s, dwAspect=%RI32, ulIndex=%RU32\n",
578 pFormatEtc->tymed, pFormatEtc->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mpFormatEtc[i].cfFormat),
579 pFormatEtc->dwAspect, i));
580 if (puIndex)
581 *puIndex = i;
582 return true;
583 }
584 }
585
586#ifdef VBOX_DND_DEBUG_FORMATS
587 LogFlowFunc(("Format NOT found: tyMed=%RI32, cfFormat=%RI16, sFormats=%s, dwAspect=%RI32\n",
588 pFormatEtc->tymed, pFormatEtc->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(pFormatEtc->cfFormat),
589 pFormatEtc->dwAspect));
590#endif
591 return false;
592}
593
594/* static */
595HGLOBAL VBoxDnDDataObject::MemDup(HGLOBAL hMemSource)
596{
597 DWORD dwLen = GlobalSize(hMemSource);
598 AssertReturn(dwLen, NULL);
599 PVOID pvSource = GlobalLock(hMemSource);
600 if (pvSource)
601 {
602 PVOID pvDest = GlobalAlloc(GMEM_FIXED, dwLen);
603 if (pvDest)
604 memcpy(pvDest, pvSource, dwLen);
605
606 GlobalUnlock(hMemSource);
607 return pvDest;
608 }
609
610 return NULL;
611}
612
613void VBoxDnDDataObject::RegisterFormat(FORMATETC *pFormatEtc, CLIPFORMAT clipFormat,
614 TYMED tyMed, LONG lIndex, DWORD dwAspect,
615 DVTARGETDEVICE *pTargetDevice)
616{
617 AssertPtr(pFormatEtc);
618
619 pFormatEtc->cfFormat = clipFormat;
620 pFormatEtc->tymed = tyMed;
621 pFormatEtc->lindex = lIndex;
622 pFormatEtc->dwAspect = dwAspect;
623 pFormatEtc->ptd = pTargetDevice;
624
625 LogFlowFunc(("Registered format=%ld, sFormat=%s\n",
626 pFormatEtc->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(pFormatEtc->cfFormat)));
627}
628
629void VBoxDnDDataObject::SetStatus(Status status)
630{
631 LogFlowFunc(("Setting status to %ld\n", status));
632 mStatus = status;
633}
634
635int VBoxDnDDataObject::Signal(const RTCString &strFormat,
636 const void *pvData, uint32_t cbData)
637{
638 LogFlowFunc(("Signalling ...\n"));
639
640 int rc;
641
642 mStatus = Dropped;
643 mstrFormat = strFormat;
644 if (cbData)
645 {
646 mpvData = RTMemAlloc(cbData);
647 if (mpvData)
648 {
649 memcpy(mpvData, pvData, cbData);
650 mcbData = cbData;
651 rc = VINF_SUCCESS;
652 }
653 else
654 rc = VERR_NO_MEMORY;
655 }
656 else
657 rc = VINF_SUCCESS;
658
659 if (RT_FAILURE(rc))
660 mStatus = Aborted;
661
662 /* Signal in any case. */
663 int rc2 = RTSemEventSignal(mSemEvent);
664 if (RT_SUCCESS(rc))
665 rc = rc2;
666
667 return rc;
668}
669
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