/* $Id: initterm.cpp 23175 2009-09-21 09:54:42Z vboxsync $ */ /** @file * MS COM / XPCOM Abstraction Layer - Initialization and Termination. */ /* * Copyright (C) 2006-2007 Sun Microsystems, Inc. * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 USA or visit http://www.sun.com if you need * additional information or have any questions. */ #if !defined (VBOX_WITH_XPCOM) #include #else /* !defined (VBOX_WITH_XPCOM) */ #include /* XPCOM_GLUE is defined when the client uses the standalone glue * (i.e. dynamically picks up the existing XPCOM shared library installation). * This is not the case for VirtualBox XPCOM clients (they are always * distrubuted with the self-built XPCOM library, and therefore have a binary * dependency on it) but left here for clarity. */ #if defined (XPCOM_GLUE) #include #endif #include #include #include #include #include #include #include #include #endif /* !defined (VBOX_WITH_XPCOM) */ #include "VBox/com/com.h" #include "VBox/com/assert.h" #include "VBox/com/EventQueue.h" #include "../include/Logging.h" #include #include #include #include #include #include #include namespace com { #if defined (VBOX_WITH_XPCOM) class DirectoryServiceProvider : public nsIDirectoryServiceProvider { public: NS_DECL_ISUPPORTS DirectoryServiceProvider() : mCompRegLocation (NULL), mXPTIDatLocation (NULL) , mComponentDirLocation (NULL), mCurrProcDirLocation (NULL) {} virtual ~DirectoryServiceProvider(); HRESULT init (const char *aCompRegLocation, const char *aXPTIDatLocation, const char *aComponentDirLocation, const char *aCurrProcDirLocation); NS_DECL_NSIDIRECTORYSERVICEPROVIDER private: char *mCompRegLocation; char *mXPTIDatLocation; char *mComponentDirLocation; char *mCurrProcDirLocation; }; NS_IMPL_ISUPPORTS1 (DirectoryServiceProvider, nsIDirectoryServiceProvider) DirectoryServiceProvider::~DirectoryServiceProvider() { if (mCompRegLocation) { RTStrFree (mCompRegLocation); mCompRegLocation = NULL; } if (mXPTIDatLocation) { RTStrFree (mXPTIDatLocation); mXPTIDatLocation = NULL; } if (mComponentDirLocation) { RTStrFree (mComponentDirLocation); mComponentDirLocation = NULL; } if (mCurrProcDirLocation) { RTStrFree (mCurrProcDirLocation); mCurrProcDirLocation = NULL; } } /** * @param aCompRegLocation Path to compreg.dat, in Utf8. * @param aXPTIDatLocation Path to xpti.data, in Utf8. */ HRESULT DirectoryServiceProvider::init (const char *aCompRegLocation, const char *aXPTIDatLocation, const char *aComponentDirLocation, const char *aCurrProcDirLocation) { AssertReturn(aCompRegLocation, NS_ERROR_INVALID_ARG); AssertReturn(aXPTIDatLocation, NS_ERROR_INVALID_ARG); int vrc = RTStrUtf8ToCurrentCP (&mCompRegLocation, aCompRegLocation); if (RT_SUCCESS(vrc)) vrc = RTStrUtf8ToCurrentCP (&mXPTIDatLocation, aXPTIDatLocation); if (RT_SUCCESS(vrc) && aComponentDirLocation) vrc = RTStrUtf8ToCurrentCP (&mComponentDirLocation, aComponentDirLocation); if (RT_SUCCESS(vrc) && aCurrProcDirLocation) vrc = RTStrUtf8ToCurrentCP (&mCurrProcDirLocation, aCurrProcDirLocation); return RT_SUCCESS(vrc) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } NS_IMETHODIMP DirectoryServiceProvider::GetFile (const char *aProp, PRBool *aPersistent, nsIFile **aRetval) { nsCOMPtr localFile; nsresult rv = NS_ERROR_FAILURE; *aRetval = nsnull; *aPersistent = PR_TRUE; const char *fileLocation = NULL; if (strcmp (aProp, NS_XPCOM_COMPONENT_REGISTRY_FILE) == 0) fileLocation = mCompRegLocation; else if (strcmp (aProp, NS_XPCOM_XPTI_REGISTRY_FILE) == 0) fileLocation = mXPTIDatLocation; else if (mComponentDirLocation && strcmp (aProp, NS_XPCOM_COMPONENT_DIR) == 0) fileLocation = mComponentDirLocation; else if (mCurrProcDirLocation && strcmp (aProp, NS_XPCOM_CURRENT_PROCESS_DIR) == 0) fileLocation = mCurrProcDirLocation; else return NS_ERROR_FAILURE; rv = NS_NewNativeLocalFile (nsEmbedCString (fileLocation), PR_TRUE, getter_AddRefs (localFile)); if (NS_FAILED(rv)) return rv; return localFile->QueryInterface (NS_GET_IID (nsIFile), (void **) aRetval); } /** * Global XPCOM initialization flag (we maintain it ourselves since XPCOM * doesn't provide such functionality) */ static bool volatile gIsXPCOMInitialized = false; /** * Number of Initialize() calls on the main thread. */ static unsigned int gXPCOMInitCount = 0; #else /* !defined (VBOX_WITH_XPCOM) */ /** * The COM main thread handle. (The first caller of com::Initialize().) */ static RTTHREAD volatile gCOMMainThread = NIL_RTTHREAD; /** * Number of Initialize() calls on the main thread. */ static uint32_t gCOMMainInitCount = 0; #endif /* !defined (VBOX_WITH_XPCOM) */ /** * Initializes the COM runtime. * * This method must be called on each thread of the client application that * wants to access COM facilities. The initialization must be performed before * calling any other COM method or attempting to instantiate COM objects. * * On platforms using XPCOM, this method uses the following scheme to search for * XPCOM runtime: * * 1. If the VBOX_APP_HOME environment variable is set, the path it specifies * is used to search XPCOM libraries and components. If this method fails to * initialize XPCOM runtime using this path, it will immediately return a * failure and will NOT check for other paths as described below. * * 2. If VBOX_APP_HOME is not set, this methods tries the following paths in the * given order: * * a) Compiled-in application data directory (as returned by * RTPathAppPrivateArch()) * b) "/usr/lib/virtualbox" (Linux only) * c) "/opt/VirtualBox" (Linux only) * * The first path for which the initialization succeeds will be used. * * On MS COM platforms, the COM runtime is provided by the system and does not * need to be searched for. * * Once the COM subsystem is no longer necessary on a given thread, Shutdown() * must be called to free resources allocated for it. Note that a thread may * call Initialize() several times but for each of tese calls there must be a * corresponding Shutdown() call. * * @return S_OK on success and a COM result code in case of failure. */ HRESULT Initialize() { HRESULT rc = E_FAIL; #if !defined (VBOX_WITH_XPCOM) DWORD flags = COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY; rc = CoInitializeEx (NULL, flags); /// @todo the below rough method of changing the aparment type doesn't /// work on some systems for unknown reason (CoUninitialize() simply does /// nothing there, or at least all 10 000 of subsequent CoInitializeEx() /// continue to return RPC_E_CHANGED_MODE there). The problem on those /// systems is related to the "Extend support for advanced text services /// to all programs" checkbox in the advanced language settings dialog, /// i.e. the problem appears when this checkbox is checked and disappears /// if you clear it. For this reason, we disable the code below and /// instead initialize COM in MTA as early as possible, before 3rd party /// libraries we use have done so (i.e. Qt). # if 0 /* If we fail to set the necessary apartment model, it may mean that some * DLL that was indirectly loaded by the process calling this function has * already initialized COM on the given thread in an incompatible way * which we can't leave with. Therefore, we try to fix this by using the * brute force method: */ if (rc == RPC_E_CHANGED_MODE) { /* Before we use brute force, we need to check if we are in the * neutral threaded apartment -- in this case there is no need to * worry at all. */ rc = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); if (rc == RPC_E_CHANGED_MODE) { /* This is a neutral apartment, reset the error */ rc = S_OK; LogFlowFunc (("COM is already initialized in neutral threaded " "apartment mode,\nwill accept it.\n")); } else if (rc == S_FALSE) { /* balance the test CoInitializeEx above */ CoUninitialize(); rc = RPC_E_CHANGED_MODE; LogFlowFunc (("COM is already initialized in single threaded " "apartment mode,\nwill reinitialize as " "multi threaded.\n")); enum { MaxTries = 10000 }; int tries = MaxTries; while (rc == RPC_E_CHANGED_MODE && tries --) { CoUninitialize(); rc = CoInitializeEx (NULL, flags); if (rc == S_OK) { /* We've successfully reinitialized COM; restore the * initialization reference counter */ LogFlowFunc (("Will call CoInitializeEx() %d times.\n", MaxTries - tries)); while (tries ++ < MaxTries) { rc = CoInitializeEx (NULL, flags); Assert (rc == S_FALSE); } } } } else AssertMsgFailed (("rc=%08X\n", rc)); } # endif /* the overall result must be either S_OK or S_FALSE (S_FALSE means * "already initialized using the same apartment model") */ AssertMsg (rc == S_OK || rc == S_FALSE, ("rc=%08X\n", rc)); /* To be flow compatible with the XPCOM case, we return here if this isn't * the main thread or if it isn't its first initialization call. * Note! CoInitializeEx and CoUninitialize does it's own reference * counting, so this exercise is entirely for the EventQueue init. */ bool fRc; RTTHREAD hSelf = RTThreadSelf (); if (hSelf != NIL_RTTHREAD) ASMAtomicCmpXchgHandle (&gCOMMainThread, hSelf, NIL_RTTHREAD, fRc); else fRc = false; if (!fRc) { if ( gCOMMainThread == hSelf && SUCCEEDED (rc)) gCOMMainInitCount++; AssertComRC (rc); return rc; } Assert (RTThreadIsMain (hSelf)); /* this is the first main thread initialization */ Assert (gCOMMainInitCount == 0); if (SUCCEEDED (rc)) gCOMMainInitCount = 1; #else /* !defined (VBOX_WITH_XPCOM) */ if (ASMAtomicXchgBool (&gIsXPCOMInitialized, true) == true) { /* XPCOM is already initialized on the main thread, no special * initialization is necessary on additional threads. Just increase * the init counter if it's a main thread again (to correctly support * nested calls to Initialize()/Shutdown() for compatibility with * Win32). */ nsCOMPtr eventQ; rc = NS_GetMainEventQ (getter_AddRefs (eventQ)); if (NS_SUCCEEDED(rc)) { PRBool isOnMainThread = PR_FALSE; rc = eventQ->IsOnCurrentThread (&isOnMainThread); if (NS_SUCCEEDED(rc) && isOnMainThread) ++ gXPCOMInitCount; } AssertComRC (rc); return rc; } Assert (RTThreadIsMain (RTThreadSelf())); /* this is the first initialization */ gXPCOMInitCount = 1; bool const fInitEventQueues = true; /* prepare paths for registry files */ char userHomeDir [RTPATH_MAX]; int vrc = GetVBoxUserHomeDirectory (userHomeDir, sizeof (userHomeDir)); AssertRCReturn (vrc, NS_ERROR_FAILURE); char compReg [RTPATH_MAX]; char xptiDat [RTPATH_MAX]; /** @todo use RTPathAppend */ RTStrPrintf (compReg, sizeof (compReg), "%s%c%s", userHomeDir, RTPATH_DELIMITER, "compreg.dat"); RTStrPrintf (xptiDat, sizeof (xptiDat), "%s%c%s", userHomeDir, RTPATH_DELIMITER, "xpti.dat"); LogFlowFunc (("component registry : \"%s\"\n", compReg)); LogFlowFunc (("XPTI data file : \"%s\"\n", xptiDat)); #if defined (XPCOM_GLUE) XPCOMGlueStartup (nsnull); #endif static const char *kAppPathsToProbe[] = { NULL, /* 0: will use VBOX_APP_HOME */ NULL, /* 1: will try RTPathAppPrivateArch() */ #ifdef RT_OS_LINUX "/usr/lib/virtualbox", "/opt/VirtualBox", #elif RT_OS_SOLARIS "/opt/VirtualBox/amd64", "/opt/VirtualBox/i386", #elif RT_OS_DARWIN "/Application/VirtualBox.app/Contents/MacOS", #endif }; /* Find out the directory where VirtualBox binaries are located */ for (size_t i = 0; i < RT_ELEMENTS (kAppPathsToProbe); ++ i) { char appHomeDir [RTPATH_MAX]; appHomeDir [RTPATH_MAX - 1] = '\0'; if (i == 0) { /* Use VBOX_APP_HOME if present */ if (!RTEnvExist ("VBOX_APP_HOME")) continue; strncpy (appHomeDir, RTEnvGet ("VBOX_APP_HOME"), RTPATH_MAX - 1); /** @todo r=bird: Use RTEnvGetEx. */ } else if (i == 1) { /* Use RTPathAppPrivateArch() first */ vrc = RTPathAppPrivateArch (appHomeDir, sizeof (appHomeDir)); AssertRC (vrc); if (RT_FAILURE(vrc)) { rc = NS_ERROR_FAILURE; continue; } } else { /* Iterate over all other paths */ strncpy (appHomeDir, kAppPathsToProbe [i], RTPATH_MAX - 1); } nsCOMPtr dsProv; char compDir [RTPATH_MAX]; RTStrPrintf (compDir, sizeof (compDir), "%s%c%s", appHomeDir, RTPATH_DELIMITER, "components"); LogFlowFunc (("component directory : \"%s\"\n", compDir)); dsProv = new DirectoryServiceProvider(); if (dsProv) rc = dsProv->init (compReg, xptiDat, compDir, appHomeDir); else rc = NS_ERROR_OUT_OF_MEMORY; if (NS_FAILED (rc)) break; /* Setup the application path for NS_InitXPCOM2. Note that we properly * answer the NS_XPCOM_CURRENT_PROCESS_DIR query in our directory * service provider but it seems to be activated after the directory * service is used for the first time (see the source NS_InitXPCOM2). So * use the same value here to be on the safe side. */ nsCOMPtr appDir; { char *appDirCP = NULL; vrc = RTStrUtf8ToCurrentCP (&appDirCP, appHomeDir); if (RT_SUCCESS(vrc)) { nsCOMPtr file; rc = NS_NewNativeLocalFile (nsEmbedCString (appDirCP), PR_FALSE, getter_AddRefs (file)); if (NS_SUCCEEDED(rc)) appDir = do_QueryInterface (file, &rc); RTStrFree (appDirCP); } else rc = NS_ERROR_FAILURE; } if (NS_FAILED (rc)) break; /* Set VBOX_XPCOM_HOME to the same app path to make XPCOM sources that * still use it instead of the directory service happy */ { char *pathCP = NULL; vrc = RTStrUtf8ToCurrentCP (&pathCP, appHomeDir); if (RT_SUCCESS(vrc)) { vrc = RTEnvSet ("VBOX_XPCOM_HOME", pathCP); RTStrFree (pathCP); } AssertRC (vrc); } /* Finally, initialize XPCOM */ { nsCOMPtr serviceManager; rc = NS_InitXPCOM2 (getter_AddRefs (serviceManager), appDir, dsProv); if (NS_SUCCEEDED(rc)) { nsCOMPtr registrar = do_QueryInterface (serviceManager, &rc); if (NS_SUCCEEDED(rc)) { rc = registrar->AutoRegister (nsnull); if (NS_SUCCEEDED(rc)) { /* We succeeded, stop probing paths */ LogFlowFunc (("Succeeded.\n")); break; } } } } /* clean up before the new try */ rc = NS_ShutdownXPCOM (nsnull); if (i == 0) { /* We failed with VBOX_APP_HOME, don't probe other paths */ break; } } #endif /* !defined (VBOX_WITH_XPCOM) */ AssertComRC (rc); /* * Init the main event queue (ASSUMES it cannot fail). */ if (SUCCEEDED (rc)) EventQueue::init(); return rc; } HRESULT Shutdown() { HRESULT rc = S_OK; #if !defined (VBOX_WITH_XPCOM) /* EventQueue::uninit reference counting fun. */ RTTHREAD hSelf = RTThreadSelf(); if ( hSelf == gCOMMainThread && hSelf != NIL_RTTHREAD) { if (-- gCOMMainInitCount == 0) { EventQueue::uninit(); ASMAtomicWriteHandle (&gCOMMainThread, NIL_RTTHREAD); } } CoUninitialize(); #else /* !defined (VBOX_WITH_XPCOM) */ nsCOMPtr eventQ; rc = NS_GetMainEventQ (getter_AddRefs (eventQ)); if (NS_SUCCEEDED(rc) || rc == NS_ERROR_NOT_AVAILABLE) { /* NS_ERROR_NOT_AVAILABLE seems to mean that * nsIEventQueue::StopAcceptingEvents() has been called (see * nsEventQueueService.cpp). We hope that this error code always means * just that in this case and assume that we're on the main thread * (it's a kind of unexpected behavior if a non-main thread ever calls * StopAcceptingEvents() on the main event queue). */ PRBool isOnMainThread = PR_FALSE; if (NS_SUCCEEDED(rc)) { rc = eventQ->IsOnCurrentThread (&isOnMainThread); eventQ = nsnull; /* early release before shutdown */ } else { isOnMainThread = PR_TRUE; rc = NS_OK; } if (NS_SUCCEEDED(rc) && isOnMainThread) { /* only the main thread needs to uninitialize XPCOM and only if * init counter drops to zero */ if (-- gXPCOMInitCount == 0) { EventQueue::uninit(); rc = NS_ShutdownXPCOM (nsnull); /* This is a thread initialized XPCOM and set gIsXPCOMInitialized to * true. Reset it back to false. */ bool wasInited = ASMAtomicXchgBool (&gIsXPCOMInitialized, false); Assert (wasInited == true); NOREF (wasInited); #if defined (XPCOM_GLUE) XPCOMGlueShutdown(); #endif } } } #endif /* !defined (VBOX_WITH_XPCOM) */ AssertComRC (rc); return rc; } } /* namespace com */