下面的代码生成一个线程,该线程等待 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();
}

错误信息是:

最佳答案

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

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

  • 创建一个窗口并显示它。注意:仅创建窗口是不够的,它实际上需要可见。
  • 在主线程中配置 CoInitializeEx 为 COINIT_MULTITHREADED。注意:在工作线程中将 CoInitializeEx 配置为 COINIT_MULTITHREADED 没有帮助。
  • 在主线程中进行枚举,完全放弃工作线程。
  • 在工作线程完成后等待 >15 秒,然后再调用 CoUninitialize。
  • 插入对 CoInitialize 的额外(未匹配)调用,以确保引用计数永远不会降至 0,因此 COM 永远不会真正未初始化。

  • 不幸的是,hacks 1-3 在这个测试用例所基于的实际代码中是不可行的。我不愿意强制用户等待超过 15 秒的时间让应用程序退出。因此,现在我倾向于 hack #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;
    }
    

    关于winapi - CoUninitialize 错误 : Access violation reading location 0x00000008,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35477970/

    10-17 01:29