Windows 控制台应用程序何时需要调用 CoInitialize

When is calling CoInitialize required for a Windows console application

下面的代码源自 https://docs.microsoft.com/en-us/windows/desktop/shell/folder-info#determining-an-objects-parent-folder,在编译时按预期工作,并且 运行 通过 Visual Studios 2017:

#include "stdafx.h"
#include <shlobj.h>
#include <shlwapi.h>
#include <objbase.h>

#pragma comment(lib, "shlwapi")

int main()
{
    IShellFolder *psfParent = NULL;
    LPITEMIDLIST pidlSystem = NULL;
    LPCITEMIDLIST pidlRelative = NULL;
    STRRET strDispName;
    TCHAR szDisplayName[MAX_PATH];
    HRESULT hr;

    hr = SHGetFolderLocation(NULL, CSIDL_SYSTEM, NULL, NULL, &pidlSystem);

    hr = SHBindToParent(pidlSystem, IID_IShellFolder, (void **)&psfParent, &pidlRelative);

    if (SUCCEEDED(hr))
    {
        hr = psfParent->GetDisplayNameOf(pidlRelative, SHGDN_NORMAL, &strDispName);
        hr = StrRetToBuf(&strDispName, pidlSystem, szDisplayName, sizeof(szDisplayName));

        _tprintf(_T("%s\n"), szDisplayName);
    }

    psfParent->Release();
    CoTaskMemFree(pidlSystem);

    Sleep(5000);

    return 0;
}

如果我将 CSIDL_SYSTEM 替换为 CSIDL_MYDOCUMENTS,但是,GetDisplayNameOf 方法调用会失败:

onecore\com\combase\objact\objact.cxx(812)\combase.dll!74EA3270: (caller: 74EA201B) ReturnHr(1) tid(d4c) 800401F0 CoInitialize has not been called.
onecoreuap\shell\windows.storage\regfldr.cpp(1260)\windows.storage.dll!76FE4FA3: (caller: 76E9F7EE) ReturnHr(1) tid(d4c) 80040111 ClassFactory cannot supply requested class

在调用 SHGetFolderLocation 之前添加 CoInitialize(NULL); 可以解决问题。

为什么在一种情况下需要调用 CoInitialize 而在另一种情况下不需要?

此外,似乎应该始终调用 CoInitialize,但有趣的是示例代码并未调用它。我很好奇为什么会这样。我无法按原样编译示例代码 - 找不到 <iostream.h>,这就是为什么我将 cout 打印代码替换为对 _tprintf 的调用...也许就是这样问题的迹象? C++ 运行time 是否为你调用 CoInitialize,也许 VS 正在尝试为我或其他东西构建 C 应用程序(比如 Linux,用 gcc 和 g++ 编译有不同的含义)。

SHGetFolderLocation 可以将执行委托给需要 COM 初始化的扩展。尽管文档没有明确说明,但您可以找到关于 ShellExecute 的注释,它是同一模块 (shell32.dll) 的一部分。

Because ShellExecute can delegate execution to Shell extensions (data sources, context menu handlers, verb implementations) that are activated using Component Object Model (COM), COM should be initialized before ShellExecute is called. Some Shell extensions require the COM single-threaded apartment (STA) type. In that case, COM should be initialized as shown here:

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)

There are certainly instances where ShellExecute does not use one of these types of Shell extension and those instances would not require COM to be initialized at all. Nonetheless, it is good practice to always initalize COM before using this function.

您可以使用以下帮助程序 class 在当前线程上自动初始化 COM 库。

class COMRuntime
{
public:
   COMRuntime() {
        ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
   }
   ~COMRuntime() {
        ::CoUninitialize();
   }
};

然后只声明一个 class:

int main()
{
   COMRuntime com;

   // the rest of your code
}

通常,您应该在创建继承自 IUnknown 的 shell COM 对象之前初始化 COM/OLE,使用拖放等。这也适用于可能使用 COM 的函数在内部,理论上可以是 shell32 和 shlwapi.

中的大部分 SH* 函数

为什么它适用于 CSIDL_SYSTEM

本Windows95本shell could run without loading COM/OLE。为此,它提供了自己的 mini-COM 实现。 Shell 扩展可以将自己标记为不需要真正的 COM,并且在 shell32 中实现的东西会调用一个特殊的 CoCreateInstance,它试图直接从 shell32 加载东西。这是为了避免加载 ole32.dll,因为在具有 4 MiB 内存的 Intel 386 机器上加载它是一个非常大的文件(Windows 95 最低要求)。

处理文件系统的 IShellFolder 实现在 shell32 中实现,不需要 COM,因此能够处理像 c:\Windows\system32 这样的路径。

然而,

CSIDL_MYDOCUMENTS 不是普通文件夹,它是命名空间扩展,其部分实现在 mydocs.dll 中。正如您所发现的,它的一部分确实需要 COM。

当然,所有这些都是实现细节,您永远不应该假设任何这些都可以在不初始化 COM 的情况下工作。