gecko-dev/xpcom/components/nsServiceManager.cpp

642 lines
19 KiB
C++
Raw Normal View History

1998-08-28 21:25:34 +00:00
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
1998-08-28 21:25:34 +00:00
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
1998-08-28 21:25:34 +00:00
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
1998-08-28 21:25:34 +00:00
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* IBM Corp.
1998-08-28 21:25:34 +00:00
*/
#include "nsIServiceManager.h"
#include "nsICategoryManager.h"
#include "nsXPIDLString.h"
#include "nsVoidArray.h"
#include "nsHashtable.h"
1998-08-28 21:25:34 +00:00
#include "prcmon.h"
#include "prthread.h" /* XXX: only used for the NSPR initialization hack (rick) */
#include "nsAutoLock.h"
1998-08-28 21:25:34 +00:00
nsIServiceManager* gServiceManager = NULL;
PRBool gShuttingDown = PR_FALSE;
nsresult
nsGetServiceByCID::operator()( const nsIID& aIID, void** aInstancePtr ) const
{
nsresult status;
// Too bad |nsServiceManager| isn't an |nsIServiceManager|, then this could have been one call
if ( mServiceManager )
status = mServiceManager->GetService(mCID, aIID, NS_REINTERPRET_CAST(nsISupports**, aInstancePtr), 0);
else
status = nsServiceManager::GetService(mCID, aIID, NS_REINTERPRET_CAST(nsISupports**, aInstancePtr), 0);
if ( !NS_SUCCEEDED(status) )
*aInstancePtr = 0;
if ( mErrorPtr )
*mErrorPtr = status;
return status;
}
nsresult
nsGetServiceByProgID::operator()( const nsIID& aIID, void** aInstancePtr ) const
{
nsresult status;
if ( mProgID )
{
// Too bad |nsServiceManager| isn't an |nsIServiceManager|, then this could have been one call
if ( mServiceManager )
status = mServiceManager->GetService(mProgID, aIID, NS_REINTERPRET_CAST(nsISupports**, aInstancePtr), 0);
else
status = nsServiceManager::GetService(mProgID, aIID, NS_REINTERPRET_CAST(nsISupports**, aInstancePtr), 0);
if ( !NS_SUCCEEDED(status) )
*aInstancePtr = 0;
}
else
status = NS_ERROR_NULL_POINTER;
if ( mErrorPtr )
*mErrorPtr = status;
return status;
}
nsresult
nsGetServiceFromCategory::operator()( const nsIID& aIID, void** aInstancePtr)
const
{
nsresult status;
nsXPIDLCString value;
nsCOMPtr<nsICategoryManager> catman =
do_GetService(NS_CATEGORYMANAGER_PROGID, &status);
if (NS_FAILED(status)) goto error;
if (!mCategory || !mEntry) {
// when categories have defaults, use that for null mEntry
status = NS_ERROR_NULL_POINTER;
goto error;
}
/* find the progID for category.entry */
status = catman->GetCategoryEntry(mCategory, mEntry,
getter_Copies(value));
if (NS_FAILED(status)) goto error;
if (!value) {
status = NS_ERROR_SERVICE_NOT_FOUND;
goto error;
}
// Too bad |nsServiceManager| isn't an |nsIServiceManager|, then
// this could have been one call.
if ( mServiceManager )
status =
mServiceManager->GetService(value, aIID,
NS_REINTERPRET_CAST(nsISupports**,
aInstancePtr), 0);
else
status =
nsServiceManager::GetService(value, aIID,
NS_REINTERPRET_CAST(nsISupports**,
aInstancePtr), 0);
if (NS_FAILED(status)) {
error:
*aInstancePtr = 0;
}
*mErrorPtr = status;
return status;
}
1998-08-28 21:25:34 +00:00
class nsServiceEntry {
public:
1998-08-28 21:47:44 +00:00
nsServiceEntry(const nsCID& cid, nsISupports* service);
1998-08-28 21:25:34 +00:00
~nsServiceEntry();
nsresult AddListener(nsIShutdownListener* listener);
nsresult RemoveListener(nsIShutdownListener* listener);
nsresult NotifyListeners(void);
1998-08-28 21:47:44 +00:00
const nsCID& mClassID;
1998-08-28 21:25:34 +00:00
nsISupports* mService;
nsVoidArray* mListeners;
PRBool mShuttingDown;
1998-08-28 21:25:34 +00:00
};
1998-08-28 21:47:44 +00:00
nsServiceEntry::nsServiceEntry(const nsCID& cid, nsISupports* service)
: mClassID(cid), mService(service), mListeners(NULL), mShuttingDown(PR_FALSE)
1998-08-28 21:25:34 +00:00
{
}
nsServiceEntry::~nsServiceEntry()
{
if (mListeners) {
NS_ASSERTION(mListeners->Count() == 0, "listeners not removed or notified");
1998-08-28 21:25:34 +00:00
#if 0
PRUint32 size = mListeners->Count();
1998-08-28 21:25:34 +00:00
for (PRUint32 i = 0; i < size; i++) {
nsIShutdownListener* listener = (nsIShutdownListener*)(*mListeners)[i];
NS_RELEASE(listener);
1998-08-28 21:25:34 +00:00
}
#endif
delete mListeners;
}
}
nsresult
nsServiceEntry::AddListener(nsIShutdownListener* listener)
{
if (listener == NULL)
return NS_OK;
if (mListeners == NULL) {
mListeners = new nsVoidArray();
1998-08-28 21:25:34 +00:00
if (mListeners == NULL)
return NS_ERROR_OUT_OF_MEMORY;
}
PRInt32 rv = mListeners->AppendElement(listener);
NS_ADDREF(listener);
return rv == -1 ? NS_ERROR_FAILURE : NS_OK;
1998-08-28 21:25:34 +00:00
}
nsresult
nsServiceEntry::RemoveListener(nsIShutdownListener* listener)
{
if (listener == NULL)
return NS_OK;
NS_ASSERTION(mListeners, "no listeners added yet");
if ( mListeners->RemoveElement(listener) )
return NS_OK;
1998-08-28 21:25:34 +00:00
NS_ASSERTION(0, "unregistered shutdown listener");
return NS_ERROR_FAILURE;
}
nsresult
nsServiceEntry::NotifyListeners(void)
{
if (mListeners) {
PRUint32 size = mListeners->Count();
1998-08-28 21:25:34 +00:00
for (PRUint32 i = 0; i < size; i++) {
nsIShutdownListener* listener = (nsIShutdownListener*)(*mListeners)[0];
nsresult rv = listener->OnShutdown(mClassID, mService);
if (NS_FAILED(rv)) return rv;
NS_RELEASE(listener);
mListeners->RemoveElementAt(0);
1998-08-28 21:25:34 +00:00
}
NS_ASSERTION(mListeners->Count() == 0, "failed to notify all listeners");
1998-08-28 21:25:34 +00:00
delete mListeners;
mListeners = NULL;
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
class nsServiceManagerImpl : public nsIServiceManager {
1998-08-28 21:25:34 +00:00
public:
NS_IMETHOD
RegisterService(const nsCID& aClass, nsISupports* aService);
NS_IMETHOD
UnregisterService(const nsCID& aClass);
1998-08-28 21:25:34 +00:00
NS_IMETHOD
GetService(const nsCID& aClass, const nsIID& aIID,
nsISupports* *result,
nsIShutdownListener* shutdownListener = NULL);
NS_IMETHOD
ReleaseService(const nsCID& aClass, nsISupports* service,
nsIShutdownListener* shutdownListener = NULL);
NS_IMETHOD
RegisterService(const char* aProgID, nsISupports* aService);
NS_IMETHOD
UnregisterService(const char* aProgID);
NS_IMETHOD
GetService(const char* aProgID, const nsIID& aIID,
nsISupports* *result,
nsIShutdownListener* shutdownListener = NULL);
NS_IMETHOD
ReleaseService(const char* aProgID, nsISupports* service,
nsIShutdownListener* shutdownListener = NULL);
nsServiceManagerImpl(void);
1998-08-28 21:25:34 +00:00
NS_DECL_ISUPPORTS
protected:
virtual ~nsServiceManagerImpl(void);
1998-08-28 21:25:34 +00:00
nsObjectHashtable/*<nsServiceEntry>*/* mServices;
PRMonitor* mMonitor;
1998-08-28 21:25:34 +00:00
};
static PRBool PR_CALLBACK
DeleteEntry(nsHashKey *aKey, void *aData, void* closure)
1998-08-28 21:25:34 +00:00
{
nsServiceEntry* entry = (nsServiceEntry*)aData;
entry->NotifyListeners();
NS_RELEASE(entry->mService);
1998-08-28 21:25:34 +00:00
delete entry;
return PR_TRUE;
}
nsServiceManagerImpl::nsServiceManagerImpl(void)
: mMonitor(0)
{
NS_INIT_REFCNT();
mServices = new nsObjectHashtable(nsnull, nsnull, // should never be cloned
DeleteEntry, nsnull,
256, PR_TRUE); // Get a threadSafe hashtable
NS_ASSERTION(mServices, "out of memory already?");
/* XXX: This is a hack to force NSPR initialization.. This should be
* removed once PR_CEnterMonitor(...) initializes NSPR... (rick)
*/
(void)PR_GetCurrentThread();
mMonitor = nsAutoMonitor::NewMonitor("nsServiceManagerImpl");
NS_ASSERTION(mMonitor, "unable to get service manager monitor. Uh oh.");
}
nsServiceManagerImpl::~nsServiceManagerImpl(void)
1998-08-28 21:25:34 +00:00
{
if (mServices) {
delete mServices;
}
if (mMonitor) {
nsAutoMonitor::DestroyMonitor(mMonitor);
mMonitor = 0;
}
1998-08-28 21:25:34 +00:00
}
NS_IMPL_ISUPPORTS1(nsServiceManagerImpl, nsIServiceManager)
1998-08-28 21:25:34 +00:00
NS_IMETHODIMP
nsServiceManagerImpl::GetService(const nsCID& aClass, const nsIID& aIID,
nsISupports* *result,
nsIShutdownListener* shutdownListener)
1998-08-28 21:25:34 +00:00
{
nsAutoMonitor mon(mMonitor);
// test this first, since there's no point in returning a service during
// shutdown -- whether it's available or not would depend on the order it
// occurs in the list
if (gShuttingDown) {
// When processing shutdown, dont process new GetService() requests
#ifdef DEBUG_dp
NS_WARN_IF_FALSE(PR_FALSE, "Creating new service on shutdown. Denied.");
#endif /* DEBUG_dp */
return NS_ERROR_UNEXPECTED;
}
1998-08-28 21:25:34 +00:00
nsresult rv = NS_OK;
nsIDKey key(aClass);
1998-08-28 21:25:34 +00:00
nsServiceEntry* entry = (nsServiceEntry*)mServices->Get(&key);
if (entry) {
nsISupports* service;
rv = entry->mService->QueryInterface(aIID, (void**)&service);
if (NS_SUCCEEDED(rv)) {
// The refcount acquired in QI() above is "returned" to
// the caller.
rv = entry->AddListener(shutdownListener);
if (NS_SUCCEEDED(rv)) {
1998-08-28 21:25:34 +00:00
*result = service;
// If someone else requested the service to be shut down,
// and we just asked to get it again before it could be
// released, then cancel their shutdown request:
if (entry->mShuttingDown) {
entry->mShuttingDown = PR_FALSE;
NS_ADDREF(service); // Released in UnregisterService
}
1998-08-28 21:25:34 +00:00
}
}
return rv;
1998-08-28 21:25:34 +00:00
}
nsISupports* service;
// We need to not be holding the service manager's monitor while calling
// CreateInstance, because it invokes user code which could try to re-enter
// the service manager:
mon.Exit();
rv = nsComponentManager::CreateInstance(aClass, NULL, aIID, (void**)&service);
mon.Enter();
// If you hit this assertion, it means that someone tried (and
// succeeded!) to get your service during your service's
// initialization. This will mean that one instance of your
// service will leak, and may mean that the client that
// successfully acquired the service got a partially constructed
// object.
NS_ASSERTION(!mServices->Get(&key), "re-entrant call to service manager created service twice!");
if (NS_SUCCEEDED(rv)) {
entry = new nsServiceEntry(aClass, service);
if (entry == NULL) {
NS_RELEASE(service);
rv = NS_ERROR_OUT_OF_MEMORY;
}
else {
rv = entry->AddListener(shutdownListener);
if (NS_SUCCEEDED(rv)) {
mServices->Put(&key, entry);
*result = service;
NS_ADDREF(service); // Released in service manager destructor
1998-08-28 21:25:34 +00:00
}
else {
NS_RELEASE(service);
delete entry;
1998-08-28 21:25:34 +00:00
}
}
}
return rv;
1998-08-28 21:25:34 +00:00
}
NS_IMETHODIMP
nsServiceManagerImpl::ReleaseService(const nsCID& aClass, nsISupports* service,
nsIShutdownListener* shutdownListener)
1998-08-28 21:25:34 +00:00
{
PRBool serviceFound = PR_FALSE;
nsresult rv = NS_OK;
1998-08-28 21:25:34 +00:00
#ifndef NS_DEBUG
// Do entry lookup only if there is a shutdownlistener to be removed.
//
// For Debug builds, Consistency check for entry always. Releasing service
// when the service is not with the servicemanager is mostly wrong.
if (shutdownListener)
#endif
{
nsAutoMonitor mon(mMonitor);
nsIDKey key(aClass);
nsServiceEntry* entry = (nsServiceEntry*)mServices->Get(&key);
1998-08-28 21:25:34 +00:00
if (entry) {
rv = entry->RemoveListener(shutdownListener);
1999-08-05 12:58:04 +00:00
serviceFound = PR_TRUE;
1998-08-28 21:25:34 +00:00
}
}
nsrefcnt cnt;
NS_RELEASE2(service, cnt);
// Consistency check: Service ref count cannot go to zero because of the
// extra addref the service manager does, unless the service has been
// unregistered (ie) not found in the service managers hash table.
//
NS_ASSERTION(cnt > 0 || !serviceFound,
"*** Service in hash table but is being deleted. Dangling pointer\n"
"*** in service manager hash table.");
return rv;
1998-08-28 21:25:34 +00:00
}
NS_IMETHODIMP
nsServiceManagerImpl::RegisterService(const nsCID& aClass, nsISupports* aService)
1998-08-28 21:25:34 +00:00
{
nsresult rv = NS_OK;
nsAutoMonitor mon(mMonitor);
nsIDKey key(aClass);
nsServiceEntry* entry = (nsServiceEntry*)mServices->Get(&key);
if (entry) {
rv = NS_ERROR_FAILURE;
}
else {
1999-05-28 05:29:26 +00:00
entry = new nsServiceEntry(aClass, aService);
if (entry == NULL)
rv = NS_ERROR_OUT_OF_MEMORY;
else {
mServices->Put(&key, entry);
NS_ADDREF(aService); // Released in DeleteEntry from UnregisterService
}
}
return rv;
}
NS_IMETHODIMP
nsServiceManagerImpl::UnregisterService(const nsCID& aClass)
{
nsresult rv = NS_OK;
nsAutoMonitor mon(mMonitor);
1998-08-28 21:25:34 +00:00
nsIDKey key(aClass);
1998-08-28 21:25:34 +00:00
nsServiceEntry* entry = (nsServiceEntry*)mServices->Get(&key);
if (entry == NULL) {
rv = NS_ERROR_SERVICE_NOT_FOUND;
1998-08-28 21:25:34 +00:00
}
else {
rv = entry->NotifyListeners(); // break the cycles
entry->mShuttingDown = PR_TRUE;
mServices->RemoveAndDelete(&key); // This will call the delete entry func
1998-08-28 21:25:34 +00:00
}
return rv;
}
////////////////////////////////////////////////////////////////////////////////
// let's do it again, this time with ProgIDs...
NS_IMETHODIMP
nsServiceManagerImpl::RegisterService(const char* aProgID, nsISupports* aService)
{
nsCID aClass;
nsresult rv;
rv = nsComponentManager::ProgIDToClassID(aProgID, &aClass);
if (NS_FAILED(rv)) return rv;
return RegisterService(aClass, aService);
}
NS_IMETHODIMP
nsServiceManagerImpl::UnregisterService(const char* aProgID)
{
nsCID aClass;
nsresult rv;
rv = nsComponentManager::ProgIDToClassID(aProgID, &aClass);
if (NS_FAILED(rv)) return rv;
return UnregisterService(aClass);
}
NS_IMETHODIMP
nsServiceManagerImpl::GetService(const char* aProgID, const nsIID& aIID,
nsISupports* *result,
nsIShutdownListener* shutdownListener)
{
nsCID aClass;
nsresult rv;
rv = nsComponentManager::ProgIDToClassID(aProgID, &aClass);
if (NS_FAILED(rv)) return rv;
return GetService(aClass, aIID, result, shutdownListener);
}
NS_IMETHODIMP
nsServiceManagerImpl::ReleaseService(const char* aProgID, nsISupports* service,
nsIShutdownListener* shutdownListener)
{
nsCID aClass;
nsresult rv;
rv = nsComponentManager::ProgIDToClassID(aProgID, &aClass);
if (NS_FAILED(rv)) return rv;
return ReleaseService(aClass, service, shutdownListener);
}
////////////////////////////////////////////////////////////////////////////////
nsresult
NS_NewServiceManager(nsIServiceManager* *result)
{
nsServiceManagerImpl* servMgr = new nsServiceManagerImpl();
if (servMgr == NULL)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(servMgr);
*result = servMgr;
return NS_OK;
1998-08-28 21:25:34 +00:00
}
////////////////////////////////////////////////////////////////////////////////
// Global service manager interface (see nsIServiceManager.h)
nsresult
nsServiceManager::GetGlobalServiceManager(nsIServiceManager* *result)
1998-08-28 21:25:34 +00:00
{
if (gShuttingDown)
return NS_ERROR_UNEXPECTED;
nsresult rv = NS_OK;
if (gServiceManager == NULL) {
// XPCOM not initialized yet. Let us do initialization of our module.
I know it's unorthodox to do a top level checkin like this, but I've got so many files in so many different directories, that I think it's the best way. I've pulled and clobber_all'd my tree and got r=dp on this checkin. Here are the touched files: M mozilla/embedding/browser/activex/src/control/MozillaBrowser.cpp M mozilla/embedding/browser/activex/src/control/MozillaBrowser.h M mozilla/js/src/xpconnect/shell/xpcshell.cpp M mozilla/netwerk/protocol/res/src/nsResProtocolHandler.cpp M mozilla/xpcom/build/nsXPComInit.cpp M mozilla/xpcom/components/nsComponentManager.cpp M mozilla/xpcom/components/nsIServiceManager.h M mozilla/xpcom/components/nsServiceManager.cpp M mozilla/xpcom/io/nsSpecialSystemDirectory.cpp M mozilla/xpcom/io/nsSpecialSystemDirectory.h M mozilla/xpcom/tests/TestBuffers.cpp M mozilla/xpcom/tests/TestPipes.cpp M mozilla/xpcom/tests/TestShutdown.cpp M mozilla/xpcom/tests/windows/TestHelloXPLoop.cpp M mozilla/xpcom/tools/registry/regExport.cpp M mozilla/xpcom/tools/registry/regxpcom.cpp M mozilla/xpinstall/stub/xpistub.cpp M mozilla/webshell/embed/ActiveX/MozillaBrowser.cpp M mozilla/webshell/embed/ActiveX/MozillaBrowser.h M mozilla/webshell/tests/viewer/nsMacMain.cpp M mozilla/webshell/tests/viewer/nsPhMain.cpp M mozilla/webshell/tests/viewer/nsWinMain.cpp M mozilla/webshell/tests/viewer/unix/gtk/nsGtkMain.cpp M mozilla/xpfe/appshell/src/nsFileLocations.cpp M mozilla/xpfe/bootstrap/nsAppRunner.cpp The heart of this checkin is a change in the signature and symantics of NS_InitXPCOM. The new signature is extern NS_COM nsresult NS_InitXPCOM(nsIServiceManager* *result, nsFileSpec* binDirectory); I filed a bug for this problem: b=23157 The original manifestation of this bug was in mozilla/netwerk/protocol/res/src/nsResProtocolHandler.cpp It used the current process directory to find resources, which is not correct when the current process is not mozilla.exe. I have added a new type to nsSpecialSystemDirectory, Moz_BinDirectory, and made nsResProtocolHandler use that value.
2000-01-06 01:05:13 +00:00
rv = NS_InitXPCOM(NULL, NULL);
1998-08-28 21:25:34 +00:00
}
// No ADDREF as we are advicing no release of this.
if (NS_SUCCEEDED(rv))
*result = gServiceManager;
return rv;
1998-08-28 21:25:34 +00:00
}
1999-03-27 02:22:33 +00:00
nsresult
nsServiceManager::ShutdownGlobalServiceManager(nsIServiceManager* *result)
{
if (gServiceManager != NULL) {
nsrefcnt cnt;
NS_RELEASE2(gServiceManager, cnt);
NS_ASSERTION(cnt == 0, "Service Manager being held past XPCOM shutdown.");
gServiceManager = NULL;
1999-03-27 02:22:33 +00:00
}
return NS_OK;
}
1998-08-28 21:25:34 +00:00
nsresult
nsServiceManager::GetService(const nsCID& aClass, const nsIID& aIID,
1998-08-28 21:25:34 +00:00
nsISupports* *result,
nsIShutdownListener* shutdownListener)
{
nsIServiceManager* mgr;
nsresult rv = GetGlobalServiceManager(&mgr);
if (NS_FAILED(rv)) return rv;
1998-08-28 21:25:34 +00:00
return mgr->GetService(aClass, aIID, result, shutdownListener);
}
nsresult
nsServiceManager::ReleaseService(const nsCID& aClass, nsISupports* service,
1998-08-28 21:25:34 +00:00
nsIShutdownListener* shutdownListener)
{
// Don't create the global service manager here because we might be shutting
// down, and releasing all the services in its destructor
if (gServiceManager)
return gServiceManager->ReleaseService(aClass, service, shutdownListener);
// If there wasn't a global service manager, just release the object:
NS_RELEASE(service);
return NS_OK;
1998-08-28 21:25:34 +00:00
}
nsresult
nsServiceManager::RegisterService(const nsCID& aClass, nsISupports* aService)
{
nsIServiceManager* mgr;
nsresult rv = GetGlobalServiceManager(&mgr);
if (NS_FAILED(rv)) return rv;
return mgr->RegisterService(aClass, aService);
}
nsresult
nsServiceManager::UnregisterService(const nsCID& aClass)
1998-08-28 21:25:34 +00:00
{
nsIServiceManager* mgr;
nsresult rv = GetGlobalServiceManager(&mgr);
if (NS_FAILED(rv)) return rv;
return mgr->UnregisterService(aClass);
1998-08-28 21:25:34 +00:00
}
////////////////////////////////////////////////////////////////////////////////
// let's do it again, this time with ProgIDs...
nsresult
nsServiceManager::GetService(const char* aProgID, const nsIID& aIID,
nsISupports* *result,
nsIShutdownListener* shutdownListener)
{
nsIServiceManager* mgr;
nsresult rv = GetGlobalServiceManager(&mgr);
if (NS_FAILED(rv)) return rv;
return mgr->GetService(aProgID, aIID, result, shutdownListener);
}
nsresult
nsServiceManager::ReleaseService(const char* aProgID, nsISupports* service,
nsIShutdownListener* shutdownListener)
{
// Don't create the global service manager here because we might
// be shutting down, and releasing all the services in its
// destructor
if (gServiceManager)
return gServiceManager->ReleaseService(aProgID, service, shutdownListener);
// If there wasn't a global service manager, just release the object:
NS_RELEASE(service);
return NS_OK;
}
nsresult
nsServiceManager::RegisterService(const char* aProgID, nsISupports* aService)
{
nsIServiceManager* mgr;
nsresult rv = GetGlobalServiceManager(&mgr);
if (NS_FAILED(rv)) return rv;
return mgr->RegisterService(aProgID, aService);
}
nsresult
nsServiceManager::UnregisterService(const char* aProgID)
{
// Don't create the global service manager here because we might
// be shutting down, and releasing all the services in its
// destructor
if (gServiceManager)
return gServiceManager->UnregisterService(aProgID);
return NS_OK;
}
1998-08-28 21:25:34 +00:00
////////////////////////////////////////////////////////////////////////////////