diff options
Diffstat (limited to 'ipc/mscom/AgileReference.cpp')
-rw-r--r-- | ipc/mscom/AgileReference.cpp | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/ipc/mscom/AgileReference.cpp b/ipc/mscom/AgileReference.cpp new file mode 100644 index 0000000000..4bdb0e683e --- /dev/null +++ b/ipc/mscom/AgileReference.cpp @@ -0,0 +1,223 @@ +/* -*- 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/. */ + +#include "mozilla/mscom/AgileReference.h" + +#include <utility> + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/mscom/Utils.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "nsDebug.h" +# include "nsPrintfCString.h" +#endif // defined(MOZILLA_INTERNAL_API) + +#if NTDDI_VERSION < NTDDI_WINBLUE + +// Declarations from Windows SDK specific to Windows 8.1 + +enum AgileReferenceOptions { + AGILEREFERENCE_DEFAULT = 0, + AGILEREFERENCE_DELAYEDMARSHAL = 1, +}; + +HRESULT WINAPI RoGetAgileReference(AgileReferenceOptions options, REFIID riid, + IUnknown* pUnk, + IAgileReference** ppAgileReference); + +#endif // NTDDI_VERSION < NTDDI_WINBLUE + +namespace mozilla { +namespace mscom { +namespace detail { + +GlobalInterfaceTableCookie::GlobalInterfaceTableCookie(IUnknown* aObject, + REFIID aIid, + HRESULT& aOutHResult) + : mCookie(0) { + IGlobalInterfaceTable* git = ObtainGit(); + MOZ_ASSERT(git); + if (!git) { + aOutHResult = E_POINTER; + return; + } + + aOutHResult = git->RegisterInterfaceInGlobal(aObject, aIid, &mCookie); + MOZ_ASSERT(SUCCEEDED(aOutHResult)); +} + +GlobalInterfaceTableCookie::~GlobalInterfaceTableCookie() { + IGlobalInterfaceTable* git = ObtainGit(); + MOZ_ASSERT(git); + if (!git) { + return; + } + + DebugOnly<HRESULT> hr = git->RevokeInterfaceFromGlobal(mCookie); +#if defined(MOZILLA_INTERNAL_API) + NS_WARNING_ASSERTION( + SUCCEEDED(hr), + nsPrintfCString("IGlobalInterfaceTable::RevokeInterfaceFromGlobal failed " + "with HRESULT 0x%08lX", + ((HRESULT)hr)) + .get()); +#else + MOZ_ASSERT(SUCCEEDED(hr)); +#endif // defined(MOZILLA_INTERNAL_API) + mCookie = 0; +} + +HRESULT GlobalInterfaceTableCookie::GetInterface(REFIID aIid, + void** aOutInterface) const { + IGlobalInterfaceTable* git = ObtainGit(); + MOZ_ASSERT(git); + if (!git) { + return E_UNEXPECTED; + } + + MOZ_ASSERT(IsValid()); + return git->GetInterfaceFromGlobal(mCookie, aIid, aOutInterface); +} + +/* static */ +IGlobalInterfaceTable* GlobalInterfaceTableCookie::ObtainGit() { + // Internally to COM, the Global Interface Table is a singleton, therefore we + // don't worry about holding onto this reference indefinitely. + static IGlobalInterfaceTable* sGit = []() -> IGlobalInterfaceTable* { + IGlobalInterfaceTable* result = nullptr; + DebugOnly<HRESULT> hr = ::CoCreateInstance( + CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER, + IID_IGlobalInterfaceTable, reinterpret_cast<void**>(&result)); + MOZ_ASSERT(SUCCEEDED(hr)); + return result; + }(); + + return sGit; +} + +} // namespace detail + +AgileReference::AgileReference() : mIid(), mHResult(E_NOINTERFACE) {} + +AgileReference::AgileReference(REFIID aIid, IUnknown* aObject) + : mIid(aIid), mHResult(E_UNEXPECTED) { + AssignInternal(aObject); +} + +AgileReference::AgileReference(AgileReference&& aOther) + : mIid(aOther.mIid), + mAgileRef(std::move(aOther.mAgileRef)), + mGitCookie(std::move(aOther.mGitCookie)), + mHResult(aOther.mHResult) { + aOther.mHResult = CO_E_RELEASED; +} + +void AgileReference::Assign(REFIID aIid, IUnknown* aObject) { + Clear(); + mIid = aIid; + AssignInternal(aObject); +} + +void AgileReference::AssignInternal(IUnknown* aObject) { + // We expect mIid to already be set + DebugOnly<IID> zeroIid = {}; + MOZ_ASSERT(mIid != zeroIid); + + /* + * There are two possible techniques for creating agile references. Starting + * with Windows 8.1, we may use the RoGetAgileReference API, which is faster. + * If that API is not available, we fall back to using the Global Interface + * Table. + */ + static const StaticDynamicallyLinkedFunctionPtr<decltype( + &::RoGetAgileReference)> + pRoGetAgileReference(L"ole32.dll", "RoGetAgileReference"); + + MOZ_ASSERT(aObject); + + if (pRoGetAgileReference && + SUCCEEDED(mHResult = + pRoGetAgileReference(AGILEREFERENCE_DEFAULT, mIid, aObject, + getter_AddRefs(mAgileRef)))) { + return; + } + + mGitCookie = new detail::GlobalInterfaceTableCookie(aObject, mIid, mHResult); + MOZ_ASSERT(mGitCookie->IsValid()); +} + +AgileReference::~AgileReference() { Clear(); } + +void AgileReference::Clear() { + mIid = {}; + mAgileRef = nullptr; + mGitCookie = nullptr; + mHResult = E_NOINTERFACE; +} + +AgileReference& AgileReference::operator=(const AgileReference& aOther) { + Clear(); + mIid = aOther.mIid; + mAgileRef = aOther.mAgileRef; + mGitCookie = aOther.mGitCookie; + mHResult = aOther.mHResult; + return *this; +} + +AgileReference& AgileReference::operator=(AgileReference&& aOther) { + Clear(); + mIid = aOther.mIid; + mAgileRef = std::move(aOther.mAgileRef); + mGitCookie = std::move(aOther.mGitCookie); + mHResult = aOther.mHResult; + aOther.mHResult = CO_E_RELEASED; + return *this; +} + +HRESULT +AgileReference::Resolve(REFIID aIid, void** aOutInterface) const { + MOZ_ASSERT(aOutInterface); + // This check is exclusive-OR; we should have one or the other, but not both + MOZ_ASSERT((mAgileRef || mGitCookie) && !(mAgileRef && mGitCookie)); + MOZ_ASSERT(IsCOMInitializedOnCurrentThread()); + + if (!aOutInterface) { + return E_INVALIDARG; + } + + *aOutInterface = nullptr; + + if (mAgileRef) { + // IAgileReference lets you directly resolve the interface you want... + return mAgileRef->Resolve(aIid, aOutInterface); + } + + if (!mGitCookie) { + return E_UNEXPECTED; + } + + RefPtr<IUnknown> originalInterface; + HRESULT hr = + mGitCookie->GetInterface(mIid, getter_AddRefs(originalInterface)); + if (FAILED(hr)) { + return hr; + } + + if (aIid == mIid) { + originalInterface.forget(aOutInterface); + return S_OK; + } + + // ...Whereas the GIT requires us to obtain the same interface that we + // requested and then QI for the desired interface afterward. + return originalInterface->QueryInterface(aIid, aOutInterface); +} + +} // namespace mscom +} // namespace mozilla |