简体   繁体   中英

When is calling CoInitialize required for a Windows console application

The code below, derived from https://docs.microsoft.com/en-us/windows/desktop/shell/folder-info#determining-an-objects-parent-folder , works as expected when compiled and run via 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;
}

If I replace CSIDL_SYSTEM with CSIDL_MYDOCUMENTS , though, the GetDisplayNameOf method call fails with:

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

Adding CoInitialize(NULL); before the call to SHGetFolderLocation fixes the issue.

Why is calling CoInitialize required in one case but not the other?

Also, it seems like CoInitialize should always be called, but it's interesting that the sample code doesn't call it. I'm curious why this is the case. I couldn't get the sample code compiling as is - <iostream.h> couldn't be found, which is why I replaced the cout printing code with a call to _tprintf ... Maybe that's an indication of the problem? Does the C++ runtime call CoInitialize for you, and maybe VS is trying to build a C application for me or something (like how on Linux, compiling with gcc and g++ has different implications).

As a rule, you should initialize COM/OLE before creating shell COM objects that inherit from IUnknown , use drag & drop etc. This also applies to functions that might use COM internally which could in theory be most of the SH* functions in shell32 and shlwapi.

Why did it work with CSIDL_SYSTEM ?

The Windows 95 shell could run without loading COM/OLE . To do this it provided its own mini-COM implementation. Shell extensions could mark themselves as not requiring real COM and things implemented inside shell32 would call a special CoCreateInstance that tries to load things directly from shell32. This was to avoid loading ole32.dll because it is a very big file to load on a Intel 386 machine with 4 MiB of RAM (Windows 95 minimum requirements).

The IShellFolder implementation that deals with the filesystem is implemented in shell32 and does not require COM and is therefore able to handle a path like c:\\Windows\\system32 .

CSIDL_MYDOCUMENTS however, is not a normal folder, it is a namespace extension and parts of its implementation is in mydocs.dll. And as you found out, parts of it does require COM.

All of this is of course a implementation detail and you should never assume that any of this is going to work without initializing COM.

SHGetFolderLocation may delegate execution to an extension that requires COM initialization. Although the documentation does not explicitly say so, you can find a remark about that for ShellExecute which is part of the same module (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.

You can use the following helper class to automatically initialize the COM library on the current thread.

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

Then just declare one instance of that class:

int main()
{
   COMRuntime com;

   // the rest of your code
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM