Bug 1338596: Add support for agile references to mscom; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Fri, 10 Feb 2017 11:18:26 -0700
changeset 488202 4aea23a8ae9f346e9ee162ef247e2bdc023fe464
parent 487566 62398bb4dacee5bbb4d7d2b4dd8c9b2bcbeab27a
child 488204 058775b1102801be51e4ab30be669b7aa4509e4d
push id46453
push useraklotz@mozilla.com
push dateWed, 22 Feb 2017 19:09:10 +0000
reviewersjimm
bugs1338596
milestone54.0a1
Bug 1338596: Add support for agile references to mscom; r?jimm MozReview-Commit-ID: 1NZoFZntO3g
ipc/mscom/AgileReference.cpp
ipc/mscom/AgileReference.h
ipc/mscom/DynamicallyLinkedFunctionPtr.h
ipc/mscom/moz.build
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/AgileReference.cpp
@@ -0,0 +1,152 @@
+/* -*- 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 "DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Move.h"
+
+#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 {
+
+AgileReference::AgileReference(REFIID aIid, IUnknown* aObject)
+  : mIid(aIid)
+  , mGitCookie(0)
+{
+  /*
+   * 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 DynamicallyLinkedFunctionPtr<decltype(&::RoGetAgileReference)>
+    pRoGetAgileReference(L"ole32.dll", "RoGetAgileReference");
+
+  MOZ_ASSERT(aObject);
+
+  if (pRoGetAgileReference &&
+      SUCCEEDED(pRoGetAgileReference(AGILEREFERENCE_DEFAULT, aIid, aObject,
+                                     getter_AddRefs(mAgileRef)))) {
+    return;
+  }
+
+  IGlobalInterfaceTable* git = ObtainGit();
+  MOZ_ASSERT(git);
+  if (!git) {
+    return;
+  }
+
+  DebugOnly<HRESULT> hr = git->RegisterInterfaceInGlobal(aObject, aIid,
+                                                         &mGitCookie);
+  MOZ_ASSERT(SUCCEEDED(hr));
+}
+
+AgileReference::AgileReference(AgileReference&& aOther)
+  : mIid(aOther.mIid)
+  , mAgileRef(Move(aOther.mAgileRef))
+  , mGitCookie(aOther.mGitCookie)
+{
+  aOther.mGitCookie = 0;
+}
+
+AgileReference::~AgileReference()
+{
+  if (!mGitCookie) {
+    return;
+  }
+
+  IGlobalInterfaceTable* git = ObtainGit();
+  MOZ_ASSERT(git);
+  if (!git) {
+    return;
+  }
+
+  DebugOnly<HRESULT> hr = git->RevokeInterfaceFromGlobal(mGitCookie);
+  MOZ_ASSERT(SUCCEEDED(hr));
+}
+
+HRESULT
+AgileReference::Resolve(REFIID aIid, void** aOutInterface)
+{
+  MOZ_ASSERT(aOutInterface);
+  MOZ_ASSERT(mAgileRef || mGitCookie);
+
+  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;
+  }
+
+  IGlobalInterfaceTable* git = ObtainGit();
+  MOZ_ASSERT(git);
+  if (!git) {
+    return E_UNEXPECTED;
+  }
+
+  RefPtr<IUnknown> originalInterface;
+  HRESULT hr = git->GetInterfaceFromGlobal(mGitCookie, 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);
+}
+
+IGlobalInterfaceTable*
+AgileReference::ObtainGit()
+{
+  // Internally to COM, the Global Interface Table is a singleton, therefore we
+  // don't worry about holding onto this reference indefinitely.
+  static IGlobalInterfaceTable * const sGit = []() -> IGlobalInterfaceTable * const {
+    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 mscom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/AgileReference.h
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+#ifndef mozilla_mscom_AgileReference_h
+#define mozilla_mscom_AgileReference_h
+
+#include "mozilla/RefPtr.h"
+
+#include <objidl.h>
+
+namespace mozilla {
+namespace mscom {
+
+/**
+ * This class encapsulates an "agile reference." These are references that
+ * allow you to pass COM interfaces between apartments. When you have an
+ * interface that you would like to pass between apartments, you wrap that
+ * interface in an AgileReference and pass the agile reference instead. Then
+ * you unwrap the interface by calling AgileReference::Resolve.
+ *
+ * Sample usage:
+ *
+ * // In the multithreaded apartment, foo is an IFoo*
+ * auto myAgileRef = MakeUnique<AgileReference>(IID_IFoo, foo);
+ *
+ * // myAgileRef is passed to our main thread, which runs in a single-threaded
+ * // apartment:
+ *
+ * RefPtr<IFoo> foo;
+ * HRESULT hr = myAgileRef->Resolve(IID_IFoo, getter_AddRefs(foo));
+ * // Now foo may be called from the main thread
+ */
+class AgileReference
+{
+public:
+  AgileReference(REFIID aIid, IUnknown* aObject);
+  AgileReference(AgileReference&& aOther);
+
+  ~AgileReference();
+
+  explicit operator bool() const
+  {
+    return mAgileRef || mGitCookie;
+  }
+
+  HRESULT Resolve(REFIID aIid, void** aOutInterface);
+
+  AgileReference(const AgileReference& aOther) = delete;
+  AgileReference& operator=(const AgileReference& aOther) = delete;
+  AgileReference& operator=(AgileReference&& aOther) = delete;
+
+private:
+  IGlobalInterfaceTable* ObtainGit();
+
+private:
+  REFIID                  mIid;
+  RefPtr<IAgileReference> mAgileRef;
+  DWORD                   mGitCookie;
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_AgileReference_h
--- a/ipc/mscom/DynamicallyLinkedFunctionPtr.h
+++ b/ipc/mscom/DynamicallyLinkedFunctionPtr.h
@@ -25,33 +25,40 @@ public:
   DynamicallyLinkedFunctionPtr(const wchar_t* aLibName, const char* aFuncName)
     : mModule(NULL)
     , mFunction(nullptr)
   {
     mModule = ::LoadLibraryW(aLibName);
     if (mModule) {
       mFunction = reinterpret_cast<FunctionPtrT>(
                     ::GetProcAddress(mModule, aFuncName));
+
+      if (!mFunction) {
+        // Since the function doesn't exist, there is no point in holding a
+        // reference to mModule anymore.
+        ::FreeLibrary(mModule);
+        mModule = NULL;
+      }
     }
   }
 
   DynamicallyLinkedFunctionPtr(const DynamicallyLinkedFunctionPtr&) = delete;
   DynamicallyLinkedFunctionPtr& operator=(const DynamicallyLinkedFunctionPtr&) = delete;
 
   DynamicallyLinkedFunctionPtr(DynamicallyLinkedFunctionPtr&&) = delete;
   DynamicallyLinkedFunctionPtr& operator=(DynamicallyLinkedFunctionPtr&&) = delete;
 
   ~DynamicallyLinkedFunctionPtr()
   {
     if (mModule) {
       ::FreeLibrary(mModule);
     }
   }
 
-  R operator()(Args... args)
+  R operator()(Args... args) const
   {
     return mFunction(mozilla::Forward<Args>(args)...);
   }
 
   explicit operator bool() const
   {
     return !!mFunction;
   }
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -1,27 +1,29 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.mozilla.mscom += [
     'Aggregation.h',
+    'AgileReference.h',
     'AsyncInvoker.h',
     'COMApartmentRegion.h',
     'COMPtrHolder.h',
     'EnsureMTA.h',
     'MainThreadRuntime.h',
     'ProxyStream.h',
     'Ptr.h',
     'Utils.h',
 ]
 
 UNIFIED_SOURCES += [
+    'AgileReference.cpp',
     'EnsureMTA.cpp',
     'MainThreadRuntime.cpp',
     'ProxyStream.cpp',
     'Utils.cpp',
 ]
 
 if CONFIG['ACCESSIBILITY']:
     EXPORTS.mozilla.mscom += [
@@ -53,10 +55,10 @@ LOCAL_INCLUDES += [
     '/xpcom/base',
     '/xpcom/build',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
-with Files("**"):
-    BUG_COMPONENT = ("Core", "Disability Access APIs")
+with Files("**"):
+    BUG_COMPONENT = ("Core", "Disability Access APIs")