CoUninitialize错误:访问冲突读取位置0x00000008

时间:2016-02-18 09:48:37

标签: winapi com

下面的代码产生一个线程,在前台应用程序中的所有可访问(小部件)上迭代(递归)之前等待5秒。

如果(在5秒延迟期间)我切换到Windows 10 Metro应用程序(如Calc或Edge),则主线程中对CoUninitialize的调用将导致访问冲突。为什么呢?

#include <future>
#include <chrono>

#include <windows.h>
#include <oleacc.h>
#pragma comment(lib,"Oleacc.lib")

// Adapted from https://msdn.microsoft.com/en-us/library/windows/desktop/dd317975%28v=vs.85%29.aspx
HRESULT WalkTreeWithAccessibleChildren(IAccessible* pAcc, int depth)
{
  HRESULT hr;
  long childCount;
  long returnCount;

  if (!pAcc)
  {
    return E_INVALIDARG;
  }
  hr = pAcc->get_accChildCount(&childCount);
  if (FAILED(hr))
  {
    return hr;
  };
  if (childCount == 0)
  {
    return S_FALSE;
  }
  VARIANT* pArray = new VARIANT[childCount];
  hr = AccessibleChildren(pAcc, 0L, childCount, pArray, &returnCount);
  if (FAILED(hr))
  {
    return hr;
  };

  // Iterate through children.
  for (int x = 0; x < returnCount; x++)
  {
    VARIANT vtChild = pArray[x];
    // If it's an accessible object, get the IAccessible, and recurse.
    if (vtChild.vt == VT_DISPATCH)
    {
      IDispatch* pDisp = vtChild.pdispVal;
      IAccessible* pChild = NULL;
      hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pChild);
      if (hr == S_OK)
      {
        WalkTreeWithAccessibleChildren(pChild, depth + 1);
        pChild->Release();
      }
      pDisp->Release();
    }
  }
  delete[] pArray;
  return S_OK;
}

int main(int argc, char *argv[])
{
  CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

  auto future = std::async(std::launch::async,
    []
    {
      // Switch to a Windows 10 Metro app like the Calculator or Edge.
      std::this_thread::sleep_for(std::chrono::milliseconds(5000));

      auto hwnd = GetForegroundWindow();
      if (!hwnd) abort();

      CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
      IAccessible* pAcc = NULL;
      HRESULT hr = AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, IID_IAccessible, (void**)&pAcc);
      if (hr == S_OK) {
        WalkTreeWithAccessibleChildren(pAcc, 0);
        pAcc->Release();
      }
      CoUninitialize();
    }
  );
  future.wait();

  CoUninitialize();
}

错误消息是:

  

Test.exe中0x7722B9E7(combase.dll)的未处理异常:0xC0000005:访问冲突读取位置0x00000008。

1 个答案:

答案 0 :(得分:1)

根据@ RemyLebeau的建议,我添加了代码来检查lambda中CoInitialize(nullptr, COINIT_APARTMENTTHREADED)的返回值。事实证明它失败了0x80010106(设置后无法更改线程模式)。即使我拖拽代码并在lambda的最开始调用它,它也失败了。这表明MSVS的std::async实现实际上在调用lambda(wtf!)之前在线程中创建了一个多线程单元。最后,我能够通过直接使用WINAPI(即CreateThread)来避免此问题。 仅此修复程序不足以防止访问冲突。

我还没有发现正确修复访问冲突的方法,但我发现了一些阻止它发生的黑客攻击:

  1. 创建一个窗口并显示它。注意:创建窗口本身是不够的,它实际上需要可见。
  2. 将主线程中的CoInitializeEx配置为COINIT_MULTITHREADED。注意:在工作线程中配置CoInitializeEx为COINIT_MULTITHREADED 帮助。
  3. 在主线程中执行枚举并完全放弃工作线程。
  4. 等待&gt;工作线程完成后15秒,然后再调用CoUninitialize。
  5. CoInitialize插入一个额外的(不匹配的)调用,以确保引用计数永远不会降至0,因此COM永远不会真正未初始化。
  6. 不幸的是,hacks 1-3在这个测试用例所基于的真实代码中是不可行的。我不愿强迫用户等待> 15秒以便应用程序退出。因此,现在我倾向于破解#5。

    客户端本身的任何资源泄漏都不是那么重要,因为进程将退出并且操作系统将回收资源(尽管它会阻碍任何泄漏测试)。重要的是它会导致可访问性服务器(MicrosoftEdge.exe)在运行测试用例时多次泄漏几KB内存。

    修订后的代码实现了CreateThread修复以及所有5个'黑客'。必须至少启用其中一个黑客以防止访问冲突:

    #define HACK 0 // Set this between 1-5 to enable one of the hacks.
    
    #include <future>
    #include <chrono>
    
    #include <windows.h>
    #include <oleacc.h>
    #pragma comment(lib,"Oleacc.lib")
    
    // Adapted from https://msdn.microsoft.com/en-us/library/windows/desktop/dd317975%28v=vs.85%29.aspx
    HRESULT WalkTreeWithAccessibleChildren(IAccessible* pAcc, int depth)
    {
      HRESULT hr;
      long childCount;
      long returnCount;
    
      if (!pAcc)
      {
        return E_INVALIDARG;
      }
      hr = pAcc->get_accChildCount(&childCount);
      if (FAILED(hr))
      {
        return hr;
      };
      if (childCount == 0)
      {
        return S_FALSE;
      }
      VARIANT* pArray = new VARIANT[childCount];
      hr = AccessibleChildren(pAcc, 0L, childCount, pArray, &returnCount);
      if (FAILED(hr))
      {
        delete[] pArray;
        return hr;
      };
    
      // Iterate through children.
      for (int x = 0; x < returnCount; x++)
      {
        VARIANT vtChild = pArray[x];
        // If it's an accessible object, get the IAccessible, and recurse.
        if (vtChild.vt == VT_DISPATCH)
        {
          IDispatch* pDisp = vtChild.pdispVal;
          IAccessible* pChild = NULL;
          hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pChild);
          if (hr == S_OK)
          {
            WalkTreeWithAccessibleChildren(pChild, depth + 1);
            pChild->Release();
          }
          pDisp->Release();
        }
      }
      delete[] pArray;
      return S_OK;
    }
    
    DWORD WINAPI ThreadProc(LPVOID lpParameter)
    {
      HRESULT result{};
    
      // Switch to a Windows 10 Metro app like the Calculator or Edge.
      std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    
      auto hwnd = GetForegroundWindow();
      if (!hwnd) {
        abort();
      }
    
      result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
      if (FAILED(result)) {
        abort();
      }
    
      IAccessible* pAcc = NULL;
      result = AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, IID_IAccessible, (void**)&pAcc);
      if (result == S_OK) {
        WalkTreeWithAccessibleChildren(pAcc, 0);
        pAcc->Release();
      }
    
      CoUninitialize();
    
      return 0;
    }
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
      _In_opt_ HINSTANCE hPrevInstance,
      _In_ LPTSTR    lpCmdLine,
      _In_ int       nCmdShow)
    {
      HRESULT result{};
      DWORD dw{};
    
    #if HACK == 1
      HWND hwnd = CreateWindowA("STATIC", nullptr, 0,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        0, 0, 0, nullptr);
      if (!hwnd) {
        abort();
      }
      ShowWindow(hwnd, SW_SHOWNORMAL);
    #endif
    
      result = CoInitializeEx(nullptr,
    #if HACK == 2
        COINIT_MULTITHREADED
    #else
        COINIT_APARTMENTTHREADED
    #endif
      );
      if (FAILED(result)) {
        abort();
      }
    
    #if HACK == 3
      ThreadProc(nullptr);
    #else
      HANDLE threadHandle = CreateThread(nullptr, 0, &ThreadProc, nullptr, 0, nullptr);
      if (!threadHandle) {
        auto error = GetLastError();
        abort();
      }
    
      dw = WaitForSingleObject(threadHandle, INFINITE);
      if (dw == WAIT_FAILED) {
        auto error = GetLastError();
        abort();
      }
    #endif
    
    #if HACK == 4
      std::this_thread::sleep_for(std::chrono::milliseconds(16000));
    #endif
    
    #if HACK == 5
      CoInitialize(nullptr);
    #endif
    
      CoUninitialize();
    
      return 0;
    }