summaryrefslogtreecommitdiff
path: root/src/VBox/Main/ConsoleImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/ConsoleImpl.cpp')
-rw-r--r--src/VBox/Main/ConsoleImpl.cpp410
1 files changed, 210 insertions, 200 deletions
diff --git a/src/VBox/Main/ConsoleImpl.cpp b/src/VBox/Main/ConsoleImpl.cpp
index e1faceff1..a90d1161c 100644
--- a/src/VBox/Main/ConsoleImpl.cpp
+++ b/src/VBox/Main/ConsoleImpl.cpp
@@ -1,4 +1,4 @@
-/* $Id: ConsoleImpl.cpp 29580 2010-05-17 18:23:00Z vboxsync $ */
+/* $Id: ConsoleImpl.cpp 29971 2010-06-02 08:51:13Z vboxsync $ */
/** @file
* VBox Console COM Class implementation
*/
@@ -211,13 +211,11 @@ struct VMPowerUpTask : public VMProgressTask
VMPowerUpTask(Console *aConsole,
Progress *aProgress)
: VMProgressTask(aConsole, aProgress, false /* aUsesVMPtr */),
- mSetVMErrorCallback(NULL),
mConfigConstructor(NULL),
mStartPaused(false),
mTeleporterEnabled(FALSE)
{}
- PFNVMATERROR mSetVMErrorCallback;
PFNCFGMCONSTRUCTOR mConfigConstructor;
Utf8Str mSavedStateFile;
Console::SharedFolderDataMap mSharedFolders;
@@ -1684,7 +1682,7 @@ STDMETHODIMP Console::PowerDown(IProgress **aProgress)
case MachineState_Saved:
return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down a saved virtual machine"));
case MachineState_Stopping:
- return setError(VBOX_E_INVALID_VM_STATE, tr("Virtual machine is being powered down"));
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is being powered down"));
default:
return setError(VBOX_E_INVALID_VM_STATE,
tr("Invalid machine state: %s (must be Running, Paused or Stuck)"),
@@ -3416,106 +3414,108 @@ HRESULT Console::onNetworkAdapterChange(INetworkAdapter *aNetworkAdapter, BOOL c
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Don't do anything if the VM isn't running */
- if (!mpVM)
- return S_OK;
-
- /* protect mpVM */
- AutoVMCaller autoVMCaller(this);
- if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ HRESULT rc = S_OK;
- /* Get the properties we need from the adapter */
- BOOL fCableConnected, fTraceEnabled;
- HRESULT rc = aNetworkAdapter->COMGETTER(CableConnected)(&fCableConnected);
- AssertComRC(rc);
- if (SUCCEEDED(rc))
- {
- rc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fTraceEnabled);
- AssertComRC(rc);
- }
- if (SUCCEEDED(rc))
+ /* don't trigger network change if the VM isn't running */
+ if (mpVM)
{
- ULONG ulInstance;
- rc = aNetworkAdapter->COMGETTER(Slot)(&ulInstance);
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+
+ /* Get the properties we need from the adapter */
+ BOOL fCableConnected, fTraceEnabled;
+ rc = aNetworkAdapter->COMGETTER(CableConnected)(&fCableConnected);
AssertComRC(rc);
if (SUCCEEDED(rc))
{
- /*
- * Find the pcnet instance, get the config interface and update
- * the link state.
- */
- NetworkAdapterType_T adapterType;
- rc = aNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ rc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fTraceEnabled);
+ AssertComRC(rc);
+ }
+ if (SUCCEEDED(rc))
+ {
+ ULONG ulInstance;
+ rc = aNetworkAdapter->COMGETTER(Slot)(&ulInstance);
AssertComRC(rc);
- const char *pszAdapterName = NULL;
- switch (adapterType)
+ if (SUCCEEDED(rc))
{
- case NetworkAdapterType_Am79C970A:
- case NetworkAdapterType_Am79C973:
- pszAdapterName = "pcnet";
- break;
+ /*
+ * Find the pcnet instance, get the config interface and update
+ * the link state.
+ */
+ NetworkAdapterType_T adapterType;
+ rc = aNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ AssertComRC(rc);
+ const char *pszAdapterName = NULL;
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973:
+ pszAdapterName = "pcnet";
+ break;
#ifdef VBOX_WITH_E1000
- case NetworkAdapterType_I82540EM:
- case NetworkAdapterType_I82543GC:
- case NetworkAdapterType_I82545EM:
- pszAdapterName = "e1000";
- break;
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82543GC:
+ case NetworkAdapterType_I82545EM:
+ pszAdapterName = "e1000";
+ break;
#endif
#ifdef VBOX_WITH_VIRTIO
- case NetworkAdapterType_Virtio:
- pszAdapterName = "virtio-net";
- break;
+ case NetworkAdapterType_Virtio:
+ pszAdapterName = "virtio-net";
+ break;
#endif
- default:
- AssertFailed();
- pszAdapterName = "unknown";
- break;
- }
+ default:
+ AssertFailed();
+ pszAdapterName = "unknown";
+ break;
+ }
- PPDMIBASE pBase;
- int vrc = PDMR3QueryDeviceLun(mpVM, pszAdapterName, ulInstance, 0, &pBase);
- ComAssertRC(vrc);
- if (RT_SUCCESS(vrc))
- {
- Assert(pBase);
- PPDMINETWORKCONFIG pINetCfg;
- pINetCfg = PDMIBASE_QUERY_INTERFACE(pBase, PDMINETWORKCONFIG);
- if (pINetCfg)
+ PPDMIBASE pBase;
+ int vrc = PDMR3QueryDeviceLun(mpVM, pszAdapterName, ulInstance, 0, &pBase);
+ ComAssertRC(vrc);
+ if (RT_SUCCESS(vrc))
{
- Log(("Console::onNetworkAdapterChange: setting link state to %d\n",
- fCableConnected));
- vrc = pINetCfg->pfnSetLinkState(pINetCfg,
- fCableConnected ? PDMNETWORKLINKSTATE_UP
- : PDMNETWORKLINKSTATE_DOWN);
- ComAssertRC(vrc);
- }
+ Assert(pBase);
+ PPDMINETWORKCONFIG pINetCfg;
+ pINetCfg = PDMIBASE_QUERY_INTERFACE(pBase, PDMINETWORKCONFIG);
+ if (pINetCfg)
+ {
+ Log(("Console::onNetworkAdapterChange: setting link state to %d\n",
+ fCableConnected));
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg,
+ fCableConnected ? PDMNETWORKLINKSTATE_UP
+ : PDMNETWORKLINKSTATE_DOWN);
+ ComAssertRC(vrc);
+ }
#ifdef VBOX_DYNAMIC_NET_ATTACH
- if (RT_SUCCESS(vrc) && changeAdapter)
- {
- VMSTATE enmVMState = VMR3GetState(mpVM);
- if ( enmVMState == VMSTATE_RUNNING /** @todo LiveMigration: Forbit or deal correctly with the _LS variants */
- || enmVMState == VMSTATE_SUSPENDED)
+ if (RT_SUCCESS(vrc) && changeAdapter)
{
- if (fTraceEnabled && fCableConnected && pINetCfg)
+ VMSTATE enmVMState = VMR3GetState(mpVM);
+ if ( enmVMState == VMSTATE_RUNNING /** @todo LiveMigration: Forbid or deal correctly with the _LS variants */
+ || enmVMState == VMSTATE_SUSPENDED)
{
- vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_DOWN);
- ComAssertRC(vrc);
- }
-
- rc = doNetworkAdapterChange(pszAdapterName, ulInstance, 0, aNetworkAdapter);
-
- if (fTraceEnabled && fCableConnected && pINetCfg)
- {
- vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_UP);
- ComAssertRC(vrc);
+ if (fTraceEnabled && fCableConnected && pINetCfg)
+ {
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_DOWN);
+ ComAssertRC(vrc);
+ }
+
+ rc = doNetworkAdapterChange(pszAdapterName, ulInstance, 0, aNetworkAdapter);
+
+ if (fTraceEnabled && fCableConnected && pINetCfg)
+ {
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_UP);
+ ComAssertRC(vrc);
+ }
}
}
- }
#endif /* VBOX_DYNAMIC_NET_ATTACH */
- }
+ }
- if (RT_FAILURE(vrc))
- rc = E_FAIL;
+ if (RT_FAILURE(vrc))
+ rc = E_FAIL;
+ }
}
}
@@ -3723,17 +3723,17 @@ HRESULT Console::onSerialPortChange(ISerialPort *aSerialPort)
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Don't do anything if the VM isn't running */
- if (!mpVM)
- return S_OK;
-
HRESULT rc = S_OK;
- /* protect mpVM */
- AutoVMCaller autoVMCaller(this);
- if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ /* don't trigger serial port change if the VM isn't running */
+ if (mpVM)
+ {
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
- /* nothing to do so far */
+ /* nothing to do so far */
+ }
/* notify console callbacks on success */
if (SUCCEEDED(rc))
@@ -3757,17 +3757,17 @@ HRESULT Console::onParallelPortChange(IParallelPort *aParallelPort)
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Don't do anything if the VM isn't running */
- if (!mpVM)
- return S_OK;
-
HRESULT rc = S_OK;
- /* protect mpVM */
- AutoVMCaller autoVMCaller(this);
- if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ /* don't trigger parallel port change if the VM isn't running */
+ if (mpVM)
+ {
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
- /* nothing to do so far */
+ /* nothing to do so far */
+ }
/* notify console callbacks on success */
if (SUCCEEDED(rc))
@@ -3791,17 +3791,17 @@ HRESULT Console::onStorageControllerChange()
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Don't do anything if the VM isn't running */
- if (!mpVM)
- return S_OK;
-
HRESULT rc = S_OK;
- /* protect mpVM */
- AutoVMCaller autoVMCaller(this);
- if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ /* don't trigger storage controller change if the VM isn't running */
+ if (mpVM)
+ {
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
- /* nothing to do so far */
+ /* nothing to do so far */
+ }
/* notify console callbacks on success */
if (SUCCEEDED(rc))
@@ -3825,17 +3825,17 @@ HRESULT Console::onMediumChange(IMediumAttachment *aMediumAttachment, BOOL aForc
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Don't do anything if the VM isn't running */
- if (!mpVM)
- return S_OK;
-
HRESULT rc = S_OK;
- /* protect mpVM */
- AutoVMCaller autoVMCaller(this);
- if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ /* don't trigger medium change if the VM isn't running */
+ if (mpVM)
+ {
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
- rc = doMediumChange(aMediumAttachment, !!aForce);
+ rc = doMediumChange(aMediumAttachment, !!aForce);
+ }
/* notify console callbacks on success */
if (SUCCEEDED(rc))
@@ -3859,20 +3859,20 @@ HRESULT Console::onCPUChange(ULONG aCPU, BOOL aRemove)
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Don't do anything if the VM isn't running */
- if (!mpVM)
- return S_OK;
-
HRESULT rc = S_OK;
- /* protect mpVM */
- AutoVMCaller autoVMCaller(this);
- if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ /* don't trigger CPU change if the VM isn't running */
+ if (mpVM)
+ {
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
- if (aRemove)
- rc = doCPURemove(aCPU);
- else
- rc = doCPUAdd(aCPU);
+ if (aRemove)
+ rc = doCPURemove(aCPU);
+ else
+ rc = doCPUAdd(aCPU);
+ }
/* notify console callbacks on success */
if (SUCCEEDED(rc))
@@ -3974,22 +3974,24 @@ HRESULT Console::onUSBControllerChange()
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Ignore if no VM is running yet. */
- if (!mpVM)
- return S_OK;
-
HRESULT rc = S_OK;
-/// @todo (dmik)
-// check for the Enabled state and disable virtual USB controller??
-// Anyway, if we want to query the machine's USB Controller we need to cache
-// it to mUSBController in #init() (as it is done with mDVDDrive).
-//
-// bird: While the VM supports hot-plugging, I doubt any guest can handle it at this time... :-)
-//
-// /* protect mpVM */
-// AutoVMCaller autoVMCaller(this);
-// if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+ /* don't trigger USB controller change if the VM isn't running */
+ if (mpVM)
+ {
+ /// @todo implement one day.
+ // Anyway, if we want to query the machine's USB Controller we need
+ // to cache it to mUSBController in #init() (similar to mDVDDrive).
+ //
+ // bird: While the VM supports hot-plugging, I doubt any guest can
+ // handle it at this time... :-)
+
+ /* protect mpVM */
+ AutoVMCaller autoVMCaller(this);
+ if (FAILED(autoVMCaller.rc())) return autoVMCaller.rc();
+
+ /* nothing to do so far */
+ }
/* notify console callbacks on success */
if (SUCCEEDED(rc))
@@ -4899,7 +4901,7 @@ HRESULT Console::addVMCaller(bool aQuiet /* = false */,
{
/* powerDown() is waiting for all callers to finish */
return aQuiet ? E_ACCESSDENIED : setError(E_ACCESSDENIED,
- tr("Virtual machine is being powered down"));
+ tr("The virtual machine is being powered down"));
}
if (mpVM == NULL)
@@ -4908,7 +4910,7 @@ HRESULT Console::addVMCaller(bool aQuiet /* = false */,
/* The machine is not powered up */
return aQuiet ? E_ACCESSDENIED : setError(E_ACCESSDENIED,
- tr("Virtual machine is not powered up"));
+ tr("The virtual machine is not powered up"));
}
++ mVMCallers;
@@ -5112,7 +5114,7 @@ HRESULT Console::powerUp(IProgress **aProgress, bool aPaused)
if (Global::IsOnlineOrTransient(mMachineState))
return setError(VBOX_E_INVALID_VM_STATE,
- tr("Virtual machine is already running or busy (machine state: %s)"),
+ tr("The virtual machine is already running or busy (machine state: %s)"),
Global::stringifyMachineState(mMachineState));
HRESULT rc = S_OK;
@@ -5225,10 +5227,40 @@ HRESULT Console::powerUp(IProgress **aProgress, bool aPaused)
progressDesc = tr("Teleporting virtual machine");
else
progressDesc = tr("Starting virtual machine");
- rc = powerupProgress->init(static_cast<IConsole *>(this),
- progressDesc,
- fTeleporterEnabled /* aCancelable */);
- if (FAILED(rc)) return rc;
+ if (mMachineState == MachineState_Saved || !fTeleporterEnabled)
+ rc = powerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc,
+ FALSE /* aCancelable */);
+ else
+ rc = powerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc,
+ TRUE /* aCancelable */,
+ 3 /* cOperations */,
+ 10 /* ulTotalOperationsWeight */,
+ Bstr(tr("Teleporting virtual machine")),
+ 1 /* ulFirstOperationWeight */,
+ NULL);
+ if (FAILED(rc))
+ return rc;
+
+ /* Tell VBoxSVC and Machine about the progress object so they can combine
+ proxy it to any openRemoteSession caller. */
+ rc = mControl->BeginPowerUp(powerupProgress);
+ if (FAILED(rc))
+ {
+ LogFlowThisFunc(("BeginPowerUp failed\n"));
+ return rc;
+ }
+
+ BOOL fCanceled;
+ rc = powerupProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(rc))
+ return rc;
+ if (fCanceled)
+ {
+ LogFlowThisFunc(("Canceled in BeginPowerUp\n"));
+ return setError(E_FAIL, tr("Powerup was canceled"));
+ }
/* setup task object and thread to carry out the operation
* asynchronously */
@@ -5236,7 +5268,6 @@ HRESULT Console::powerUp(IProgress **aProgress, bool aPaused)
std::auto_ptr<VMPowerUpTask> task(new VMPowerUpTask(this, powerupProgress));
ComAssertComRCRetRC(task->rc());
- task->mSetVMErrorCallback = setVMErrorCallback;
task->mConfigConstructor = configConstructor;
task->mSharedFolders = sharedFolders;
task->mStartPaused = aPaused;
@@ -6836,40 +6867,30 @@ DECLCALLBACK(int) Console::stateProgressCallback(PVM pVM, unsigned uPercent, voi
}
/**
- * VM error callback function. Called by the various VM components.
+ * @copydoc FNVMATERROR
*
- * @param pVM VM handle. Can be NULL if an error occurred before
- * successfully creating a VM.
- * @param pvUser Pointer to the VMProgressTask structure.
- * @param rc VBox status code.
- * @param pszFormat Printf-like error message.
- * @param args Various number of arguments for the error message.
- *
- * @thread EMT, VMPowerUp...
- *
- * @note The VMProgressTask structure modified by this callback is not thread
- * safe.
+ * @remarks Might be some tiny serialization concerns with access to the string
+ * object here...
*/
-/* static */ DECLCALLBACK(void)
-Console::setVMErrorCallback(PVM pVM, void *pvUser, int rc, RT_SRC_POS_DECL,
- const char *pszFormat, va_list args)
+/*static*/ DECLCALLBACK(void)
+Console::genericVMSetErrorCallback(PVM pVM, void *pvUser, int rc, RT_SRC_POS_DECL,
+ const char *pszErrorFmt, va_list va)
{
- VMProgressTask *task = static_cast<VMProgressTask *>(pvUser);
- AssertReturnVoid(task);
+ Utf8Str *pErrorText = (Utf8Str *)pvUser;
+ AssertPtr(pErrorText);
- /* we ignore RT_SRC_POS_DECL arguments to avoid confusion of end-users */
+ /* We ignore RT_SRC_POS_DECL arguments to avoid confusion of end-users. */
va_list va2;
- va_copy(va2, args); /* Have to make a copy here or GCC will break. */
+ va_copy(va2, va);
- /* append to the existing error message if any */
- if (task->mErrorMsg.length())
- task->mErrorMsg = Utf8StrFmt("%s.\n%N (%Rrc)", task->mErrorMsg.raw(),
- pszFormat, &va2, rc, rc);
+ /* Append to any the existing error message. */
+ if (pErrorText->length())
+ *pErrorText = Utf8StrFmt("%s.\n%N (%Rrc)", pErrorText->c_str(),
+ pszErrorFmt, &va2, rc, rc);
else
- task->mErrorMsg = Utf8StrFmt("%N (%Rrc)",
- pszFormat, &va2, rc, rc);
+ *pErrorText = Utf8StrFmt("%N (%Rrc)", pszErrorFmt, &va2, rc, rc);
- va_end (va2);
+ va_end(va2);
}
/**
@@ -7267,7 +7288,7 @@ DECLCALLBACK(int) Console::powerUpThread(RTTHREAD Thread, void *pvUser)
*/
alock.leave();
- vrc = VMR3Create(cCpus, task->mSetVMErrorCallback, task.get(),
+ vrc = VMR3Create(cCpus, Console::genericVMSetErrorCallback, &task->mErrorMsg,
task->mConfigConstructor, static_cast<Console *>(console),
&pVM);
@@ -7372,9 +7393,15 @@ DECLCALLBACK(int) Console::powerUpThread(RTTHREAD Thread, void *pvUser)
else if (task->mTeleporterEnabled)
{
/* -> ConsoleImplTeleporter.cpp */
- vrc = console->teleporterTrg(pVM, pMachine, task->mStartPaused, task->mProgress);
- if (RT_FAILURE(vrc) && !task->mErrorMsg.length())
- rc = E_FAIL; /* Avoid the "Missing error message..." assertion. */
+ bool fPowerOffOnFailure;
+ rc = console->teleporterTrg(pVM, pMachine, &task->mErrorMsg, task->mStartPaused,
+ task->mProgress, &fPowerOffOnFailure);
+ if (FAILED(rc) && fPowerOffOnFailure)
+ {
+ ErrorInfoKeeper eik;
+ int vrc2 = VMR3PowerOff(pVM);
+ AssertRC(vrc2);
+ }
}
else if (task->mStartPaused)
/* done */
@@ -7410,7 +7437,7 @@ DECLCALLBACK(int) Console::powerUpThread(RTTHREAD Thread, void *pvUser)
* be sticky but our error callback isn't.
*/
alock.leave();
- VMR3AtErrorDeregister(pVM, task->mSetVMErrorCallback, task.get());
+ VMR3AtErrorDeregister(pVM, Console::genericVMSetErrorCallback, &task->mErrorMsg);
/** @todo register another VMSetError callback? */
alock.enter();
}
@@ -7484,34 +7511,17 @@ DECLCALLBACK(int) Console::powerUpThread(RTTHREAD Thread, void *pvUser)
{
/* Notify the progress object of the success */
task->mProgress->notifyComplete(S_OK);
- console->mControl->SetPowerUpInfo(NULL);
}
else
{
/* The progress object will fetch the current error info */
task->mProgress->notifyComplete(rc);
- ProgressErrorInfo info(task->mProgress);
- ComObjPtr<VirtualBoxErrorInfo> errorInfo;
- rc = errorInfo.createObject();
- if (SUCCEEDED(rc))
- {
- errorInfo->init(info.getResultCode(),
- info.getInterfaceID(),
- info.getComponent(),
- info.getText());
- console->mControl->SetPowerUpInfo(errorInfo);
- }
- else
- {
- /* If it's not possible to create an IVirtualBoxErrorInfo object
- * signal success, as not signalling anything will cause a stuck
- * progress object in VBoxSVC. */
- console->mControl->SetPowerUpInfo(NULL);
- }
-
LogRel(("Power up failed (vrc=%Rrc, rc=%Rhrc (%#08X))\n", vrc, rc, rc));
}
+ /* Notify VBoxSVC and any waiting openRemoteSession progress object. */
+ console->mControl->EndPowerUp(rc);
+
#if defined(RT_OS_WINDOWS)
/* uninitialize COM */
CoUninitialize();