/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // COM registration data structures are built with C code, so we need to // simulate that in our C++ code by defining CINTERFACE before including // anything else that could possibly pull in Windows header files. #define CINTERFACE #include "mozilla/mscom/ActivationContext.h" #include "mozilla/mscom/Registration.h" #include "mozilla/mscom/Utils.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Assertions.h" #include "mozilla/Move.h" #include "mozilla/Pair.h" #include "mozilla/RefPtr.h" #include "mozilla/StaticPtr.h" #include "mozilla/Vector.h" #include "nsWindowsHelpers.h" #if defined(MOZILLA_INTERNAL_API) #include "mozilla/ClearOnShutdown.h" #include "mozilla/mscom/EnsureMTA.h" HRESULT RegisterPassthruProxy(); #else #include #endif // defined(MOZILLA_INTERNAL_API) #include #include #include #include #include /* This code MUST NOT use any non-inlined internal Mozilla APIs, as it will be compiled into DLLs that COM may load into non-Mozilla processes! */ extern "C" { // This function is defined in generated code for proxy DLLs but is not declared // in rpcproxy.h, so we need this declaration. void RPC_ENTRY GetProxyDllInfo(const ProxyFileInfo*** aInfo, const CLSID** aId); } namespace mozilla { namespace mscom { static bool GetContainingLibPath(wchar_t* aBuffer, size_t aBufferLen) { HMODULE thisModule = reinterpret_cast(GetContainingModuleHandle()); if (!thisModule) { return false; } DWORD fileNameResult = GetModuleFileName(thisModule, aBuffer, aBufferLen); if (!fileNameResult || (fileNameResult == aBufferLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { return false; } return true; } static bool BuildLibPath(RegistrationFlags aFlags, wchar_t* aBuffer, size_t aBufferLen, const wchar_t* aLeafName) { if (aFlags == RegistrationFlags::eUseBinDirectory) { if (!GetContainingLibPath(aBuffer, aBufferLen)) { return false; } if (!PathRemoveFileSpec(aBuffer)) { return false; } } else if (aFlags == RegistrationFlags::eUseSystemDirectory) { UINT result = GetSystemDirectoryW(aBuffer, static_cast(aBufferLen)); if (!result || result > aBufferLen) { return false; } } else { return false; } if (!PathAppend(aBuffer, aLeafName)) { return false; } return true; } static bool RegisterPSClsids(const ProxyFileInfo** aProxyInfo, const CLSID* aProxyClsid) { while (*aProxyInfo) { const ProxyFileInfo& curInfo = **aProxyInfo; for (unsigned short idx = 0, size = curInfo.TableSize; idx < size; ++idx) { HRESULT hr = CoRegisterPSClsid(*(curInfo.pStubVtblList[idx]->header.piid), *aProxyClsid); if (FAILED(hr)) { return false; } } ++aProxyInfo; } return true; } UniquePtr RegisterProxy() { const ProxyFileInfo** proxyInfo = nullptr; const CLSID* proxyClsid = nullptr; GetProxyDllInfo(&proxyInfo, &proxyClsid); if (!proxyInfo || !proxyClsid) { return nullptr; } IUnknown* classObject = nullptr; HRESULT hr = DllGetClassObject(*proxyClsid, IID_IUnknown, (void**)&classObject); if (FAILED(hr)) { return nullptr; } DWORD regCookie; hr = CoRegisterClassObject(*proxyClsid, classObject, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, ®Cookie); if (FAILED(hr)) { classObject->lpVtbl->Release(classObject); return nullptr; } wchar_t modulePathBuf[MAX_PATH + 1] = {0}; if (!GetContainingLibPath(modulePathBuf, ArrayLength(modulePathBuf))) { CoRevokeClassObject(regCookie); classObject->lpVtbl->Release(classObject); return nullptr; } ITypeLib* typeLib = nullptr; hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib); MOZ_ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { CoRevokeClassObject(regCookie); classObject->lpVtbl->Release(classObject); return nullptr; } #if defined(MOZILLA_INTERNAL_API) hr = RegisterPassthruProxy(); MOZ_ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { CoRevokeClassObject(regCookie); classObject->lpVtbl->Release(classObject); return nullptr; } #endif // defined(MOZILLA_INTERNAL_API) // RegisteredProxy takes ownership of classObject and typeLib references auto result(MakeUnique(classObject, regCookie, typeLib)); if (!RegisterPSClsids(proxyInfo, proxyClsid)) { return nullptr; } return result; } UniquePtr RegisterProxy(const wchar_t* aLeafName, RegistrationFlags aFlags) { wchar_t modulePathBuf[MAX_PATH + 1] = {0}; if (!BuildLibPath(aFlags, modulePathBuf, ArrayLength(modulePathBuf), aLeafName)) { return nullptr; } nsModuleHandle proxyDll(LoadLibrary(modulePathBuf)); if (!proxyDll.get()) { return nullptr; } // Instantiate an activation context so that CoGetClassObject will use any // COM metadata embedded in proxyDll's manifest to resolve CLSIDs. ActivationContextRegion actCtxRgn(proxyDll.get()); auto GetProxyDllInfoFn = reinterpret_cast( GetProcAddress(proxyDll, "GetProxyDllInfo")); if (!GetProxyDllInfoFn) { return nullptr; } const ProxyFileInfo** proxyInfo = nullptr; const CLSID* proxyClsid = nullptr; GetProxyDllInfoFn(&proxyInfo, &proxyClsid); if (!proxyInfo || !proxyClsid) { return nullptr; } // We call CoGetClassObject instead of DllGetClassObject because it forces // the COM runtime to manage the lifetime of the DLL. IUnknown* classObject = nullptr; HRESULT hr = CoGetClassObject(*proxyClsid, CLSCTX_INPROC_SERVER, nullptr, IID_IUnknown, (void**) &classObject); if (FAILED(hr)) { return nullptr; } DWORD regCookie; hr = CoRegisterClassObject(*proxyClsid, classObject, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, ®Cookie); if (FAILED(hr)) { classObject->lpVtbl->Release(classObject); return nullptr; } ITypeLib* typeLib = nullptr; hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib); MOZ_ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { CoRevokeClassObject(regCookie); classObject->lpVtbl->Release(classObject); return nullptr; } // RegisteredProxy takes ownership of proxyDll, classObject, and typeLib // references auto result(MakeUnique(reinterpret_cast(proxyDll.disown()), classObject, regCookie, typeLib)); if (!RegisterPSClsids(proxyInfo, proxyClsid)) { return nullptr; } return result; } UniquePtr RegisterTypelib(const wchar_t* aLeafName, RegistrationFlags aFlags) { wchar_t modulePathBuf[MAX_PATH + 1] = {0}; if (!BuildLibPath(aFlags, modulePathBuf, ArrayLength(modulePathBuf), aLeafName)) { return nullptr; } ITypeLib* typeLib = nullptr; HRESULT hr = LoadTypeLibEx(modulePathBuf, REGKIND_NONE, &typeLib); if (FAILED(hr)) { return nullptr; } // RegisteredProxy takes ownership of typeLib reference auto result(MakeUnique(typeLib)); return result; } RegisteredProxy::RegisteredProxy(uintptr_t aModule, IUnknown* aClassObject, uint32_t aRegCookie, ITypeLib* aTypeLib) : mModule(aModule) , mClassObject(aClassObject) , mRegCookie(aRegCookie) , mTypeLib(aTypeLib) #if defined(MOZILLA_INTERNAL_API) , mIsRegisteredInMTA(IsCurrentThreadMTA()) #endif // defined(MOZILLA_INTERNAL_API) { MOZ_ASSERT(aClassObject); MOZ_ASSERT(aTypeLib); AddToRegistry(this); } RegisteredProxy::RegisteredProxy(IUnknown* aClassObject, uint32_t aRegCookie, ITypeLib* aTypeLib) : mModule(0) , mClassObject(aClassObject) , mRegCookie(aRegCookie) , mTypeLib(aTypeLib) #if defined(MOZILLA_INTERNAL_API) , mIsRegisteredInMTA(IsCurrentThreadMTA()) #endif // defined(MOZILLA_INTERNAL_API) { MOZ_ASSERT(aClassObject); MOZ_ASSERT(aTypeLib); AddToRegistry(this); } // If we're initializing from a typelib, it doesn't matter which apartment we // run in, so mIsRegisteredInMTA may always be set to false in this case. RegisteredProxy::RegisteredProxy(ITypeLib* aTypeLib) : mModule(0) , mClassObject(nullptr) , mRegCookie(0) , mTypeLib(aTypeLib) #if defined(MOZILLA_INTERNAL_API) , mIsRegisteredInMTA(false) #endif // defined(MOZILLA_INTERNAL_API) { MOZ_ASSERT(aTypeLib); AddToRegistry(this); } RegisteredProxy::~RegisteredProxy() { DeleteFromRegistry(this); if (mTypeLib) { mTypeLib->lpVtbl->Release(mTypeLib); } if (mClassObject) { // NB: mClassObject and mRegCookie must be freed from inside the apartment // which they were created in. auto cleanupFn = [&]() -> void { ::CoRevokeClassObject(mRegCookie); mClassObject->lpVtbl->Release(mClassObject); }; #if defined(MOZILLA_INTERNAL_API) // This code only supports MTA when built internally if (mIsRegisteredInMTA) { EnsureMTA mta(cleanupFn); } else { cleanupFn(); } #else cleanupFn(); #endif // defined(MOZILLA_INTERNAL_API) } if (mModule) { ::FreeLibrary(reinterpret_cast(mModule)); } } RegisteredProxy::RegisteredProxy(RegisteredProxy&& aOther) { *this = mozilla::Forward(aOther); } RegisteredProxy& RegisteredProxy::operator=(RegisteredProxy&& aOther) { mModule = aOther.mModule; aOther.mModule = 0; mClassObject = aOther.mClassObject; aOther.mClassObject = nullptr; mRegCookie = aOther.mRegCookie; aOther.mRegCookie = 0; mTypeLib = aOther.mTypeLib; aOther.mTypeLib = nullptr; return *this; } HRESULT RegisteredProxy::GetTypeInfoForGuid(REFGUID aGuid, ITypeInfo** aOutTypeInfo) const { if (!aOutTypeInfo) { return E_INVALIDARG; } if (!mTypeLib) { return E_UNEXPECTED; } return mTypeLib->lpVtbl->GetTypeInfoOfGuid(mTypeLib, aGuid, aOutTypeInfo); } static StaticAutoPtr> sRegistry; namespace UseGetMutexForAccess { // This must not be accessed directly; use GetMutex() instead static CRITICAL_SECTION sMutex; } // UseGetMutexForAccess static CRITICAL_SECTION* GetMutex() { static CRITICAL_SECTION& mutex = []() -> CRITICAL_SECTION& { #if defined(RELEASE_OR_BETA) DWORD flags = CRITICAL_SECTION_NO_DEBUG_INFO; #else DWORD flags = 0; #endif InitializeCriticalSectionEx(&UseGetMutexForAccess::sMutex, 4000, flags); #if !defined(MOZILLA_INTERNAL_API) atexit([]() { DeleteCriticalSection(&UseGetMutexForAccess::sMutex); }); #endif return UseGetMutexForAccess::sMutex; }(); return &mutex; } /* static */ bool RegisteredProxy::Find(REFIID aIid, ITypeInfo** aTypeInfo) { AutoCriticalSection lock(GetMutex()); if (!sRegistry) { return false; } for (auto&& proxy : *sRegistry) { if (SUCCEEDED(proxy->GetTypeInfoForGuid(aIid, aTypeInfo))) { return true; } } return false; } /* static */ void RegisteredProxy::AddToRegistry(RegisteredProxy* aProxy) { MOZ_ASSERT(aProxy); AutoCriticalSection lock(GetMutex()); if (!sRegistry) { sRegistry = new Vector(); #if !defined(MOZILLA_INTERNAL_API) // sRegistry allocation is fallible outside of Mozilla processes if (!sRegistry) { return; } #endif } sRegistry->emplaceBack(aProxy); } /* static */ void RegisteredProxy::DeleteFromRegistry(RegisteredProxy* aProxy) { MOZ_ASSERT(aProxy); AutoCriticalSection lock(GetMutex()); MOZ_ASSERT(sRegistry && !sRegistry->empty()); if (!sRegistry) { return; } sRegistry->erase(std::remove(sRegistry->begin(), sRegistry->end(), aProxy), sRegistry->end()); if (sRegistry->empty()) { sRegistry = nullptr; } } #if defined(MOZILLA_INTERNAL_API) static StaticAutoPtr>> sArrayData; void RegisterArrayData(const ArrayData* aArrayData, size_t aLength) { AutoCriticalSection lock(GetMutex()); if (!sArrayData) { sArrayData = new Vector>(); ClearOnShutdown(&sArrayData, ShutdownPhase::ShutdownThreads); } sArrayData->emplaceBack(MakePair(aArrayData, aLength)); } const ArrayData* FindArrayData(REFIID aIid, ULONG aMethodIndex) { AutoCriticalSection lock(GetMutex()); if (!sArrayData) { return nullptr; } for (auto&& data : *sArrayData) { for (size_t innerIdx = 0, innerLen = data.second(); innerIdx < innerLen; ++innerIdx) { const ArrayData* array = data.first(); if (aMethodIndex == array[innerIdx].mMethodIndex && IsInterfaceEqualToOrInheritedFrom(aIid, array[innerIdx].mIid, aMethodIndex)) { return &array[innerIdx]; } } } return nullptr; } #endif // defined(MOZILLA_INTERNAL_API) } // namespace mscom } // namespace mozilla