关于c ++:Windows控制台应用程序需要何时调用CoInitialize

When is calling CoInitialize required for a Windows console application

从https://docs.microsoft.com/zh-cn/windows/desktop/shell/folder-info#determining-an-objects-parent-folder派生的以下代码在通过Visual Studio编译和运行时可以按预期工作 2017年:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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\
"
), szDisplayName);
    }

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

    Sleep(5000);

    return 0;
}

但是,如果将CSIDL_SYSTEM替换为CSIDL_MYDOCUMENTS,则GetDisplayNameOf方法调用将失败并显示:

1
2
3
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\
egfldr.cpp(1260)\\windows.storage.dll!76FE4FA3: (caller: 76E9F7EE) ReturnHr(1) tid(d4c) 80040111 ClassFactory cannot supply requested class

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

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

同样,似乎应该始终调用CoInitialize,但有趣的是示例代码没有调用它。 我很好奇为什么会这样。 我无法按原样编译示例代码-找不到,这就是为什么我用对_tprintf的调用替换了cout打印代码的原因...也许这表明了问题? C ++运行时是否为您调用CoInitialize,也许VS正在尝试为我或其他方面构建C应用程序(例如在Linux上如何使用gcc和g ++进行编译有不同的含义)。


通常,应该在创建从IUnknown继承,使用拖放等的外壳COM对象之前初始化COM / OLE。这也适用于可能在内部使用COM的函数,理论上可以是大多数SH*函数在shell32和shlwapi中。

为什么它与CSIDL_SYSTEM一起使用?

Windows 95外壳程序可以运行而无需加载COM / OLE。为此,它提供了自己的mini-COM实现。 Shell扩展可能会将自己标记为不需要真正的COM,而在shell32内实现的事物将调用特殊的CoCreateInstance,该CoCreateInstance试图直接从shell32加载事物。这是为了避免加载ole32.dll,因为它是要在具有4 MiB RAM的Intel 386计算机上加载的非常大的文件(Windows 95最低要求)。

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

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

当然,所有这些都是实现细节,并且您永远不要以为只要不初始化COM,所有这些都将起作用。


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.

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

1
2
3
4
5
6
7
8
9
10
class COMRuntime
{
public:
   COMRuntime() {
        ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
   }
   ~COMRuntime() {
        ::CoUninitialize();
   }
};

然后只需声明该类的一个实例:

1
2
3
4
5
6
int main()
{
   COMRuntime com;

   // the rest of your code
}