Когда вызывается CoInitialize для консольного приложения Windows - PullRequest
1 голос
/ 18 апреля 2019

Код ниже, полученный из 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

Добавление CoInitialize(NULL); до вызова SHGetFolderLocation устраняет проблему.

Почему вызов CoInitialize требуется в одном случае, а не в другом?

Кроме того, кажется, что CoInitialize всегда нужно вызывать, но интересно, что пример кода не вызывает его. Мне любопытно, почему это так. Я не смог получить пример компиляции кода, как есть - <iostream.h> не удалось найти, поэтому я заменил код печати cout на вызов _tprintf ... Может быть, это указывает на проблему? Вызывает ли среда выполнения C ++ CoInitialize для вас, и, возможно, VS пытается создать для меня приложение C или что-то в этом роде (например, как в Linux компиляция с помощью gcc и g ++ имеет разные последствия).

Ответы [ 2 ]

2 голосов
/ 19 апреля 2019

Как правило, вы должны инициализировать COM / OLE перед созданием COM-объектов оболочки, которые наследуются от IUnknown, использовать перетаскивание и т. Д. Это также относится к функциям, которые могут использовать COM внутри, которые теоретически могут составлять большую часть SH* работает в shell32 и shlwapi.

Почему он работает с CSIDL_SYSTEM?

Оболочка Windows 95 может работать без загрузки COM / OLE .Для этого он предоставил собственную реализацию мини-COM.Расширения оболочки могут помечать себя как не требующие реального COM, а вещи, реализованные в shell32, будут вызывать специальный CoCreateInstance, который пытается загрузить вещи непосредственно из shell32.Это было сделано для того, чтобы избежать загрузки ole32.dll, потому что это очень большой файл для загрузки на машине Intel 386 с 4 МБ ОЗУ (минимальные требования Windows 95).

Реализация IShellFolder, связанная с файловой системойреализован в shell32 и не требует COM и поэтому может обрабатывать путь как c:\Windows\system32.

CSIDL_MYDOCUMENTS однако, это не обычная папка, это расширение пространства имен и части его реализации находятся в mydocs.dll.И, как вы узнали, для некоторых его частей требуется COM.

Все это, конечно, деталь реализации, и вы никогда не должны предполагать, что все это будет работать без инициализации COM.

1 голос
/ 18 апреля 2019

SHGetFolderLocation может делегировать выполнение расширению, которое требует инициализации COM. Хотя в документации об этом явно не говорится, вы можете найти примечание об этом для ShellExecute, который является частью того же модуля (shell32.dll).

Поскольку ShellExecute может делегировать выполнение расширениям Shell (данные источники, контекстные меню, реализации глаголов), которые активируется с помощью объектной модели компонентов (COM), COM должен быть инициализируется перед вызовом ShellExecute. Некоторые расширения Shell требуется тип однопоточной квартиры (STA) COM. В таком случае, COM должен быть инициализирован, как показано здесь:

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)

В некоторых случаях ShellExecute не использует один из эти типы расширения Shell и эти экземпляры не требуют COM должен быть инициализирован вообще. Тем не менее, это хорошая практика для всегда инициализируйте COM перед использованием этой функции.

Вы можете использовать следующий вспомогательный класс для автоматической инициализации библиотеки COM в текущем потоке.

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

Затем просто объявите один экземпляр этого класса:

int main()
{
   COMRuntime com;

   // the rest of your code
}
...