/* -*- 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 #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 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 hr = ::CoCreateInstance( CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, reinterpret_cast(&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 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 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 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