Обратите также внимание, что я не уверен, может ли _shellMalloc быть статическим элементом SShellPtr. Проблема состоит в том, что статические элементы инициализируются перед WinMain. Из-за этого вся COM система может оказаться неустойчивой. С другой стороны, документация говорит, что Вы можете безопасно вызывать из другой API функции CoGetMalloc перед обращением к CoInitialize. Это не говорит о том, может ли SHGetMalloc, который делает почти то же самое, также вызываться в любое время в вашей программе. Подобно многим других случаям, когда система ужасно разработана или задокументирована, только эксперимент может ответить на такие вопросы. Добро пожаловать к нам с такими ответами.
Между прочим, если Вы нуждаетесь в интеллектуальном указателе, который использует специфическое распределение памяти для COM, то получите его, вызывая CoGetMalloc. Вы можете без опаски сделать этот _malloc статическим элементом и инициализировать его только один раз в вашей программе (ниже SComMalloc::GetMalloc тоже статический):
IMalloc* SComMalloc::_malloc = SComMalloc::GetMalloc();
IMalloc* SComMalloc::GetMalloc() {
IMalloc* malloc = 0;
if (CoGetMalloc(1, &malloc) == S_OK) return malloc;
else return 0;
}
Это – все, что надо знать, чтобы начать использовать оболочку Windows и ее COM интерфейсы. Ниже приводится пример. Оболочка Windows имеет понятие Рабочего стола, являющегося корнем «файловой» системы. Вы обращали внимание, как Windows приложения допускают пользователя, просматривают файловую систему, начинающуюся на рабочем столе? Этим способом Вы можете, например, создавать файлы непосредственно на вашем рабочем столе, двигаться между дисководами, просматривать сетевой дисковод, и т.д. Это, в действительности, Распределенная Файловая система (PMDFS — poor man's Distributed File System) ограниченного человека (?). Как ваше приложение может получить доступ к PMDFS? Просто. В качестве примера напишем код, который позволит пользователю, выбирать папку, просматривая PMDFS. Все, что мы должны сделать — это овладеть рабочим столом, позиционироваться относительно его, запустить встроенное окно просмотра и сформировать путь, который выбрал пользователь.
char path [MAX_PATH];
path [0] = ' ';
Desktop desktop;
ShPath browseRoot(desktop, unicodePath);
if (browseRoot.IsOK()) {
FolderBrowser browser(hwnd, browseRoot, BIF_RETURNONLYFSDIRS, "Select folder of your choice");
if (folder.IsOK()) {
strcpy (path, browser.GetPath());
}
}
Давайте, запустим объект desktop. Он использует интерфейс по имени IShellFolder. Обратите внимание, как мы приходим к Первому Правилу Захвата. Мы распределяем ресурсы в конструкторе, вызывая функцию API SHGetDesktopFolder. Интеллектуальный указатель интерфейса будет заботиться об управлении ресурсами (подсчет ссылок).
class Desktop: public SIfacePtr<IShellFolder> {
public:
Desktop() {
if (SHGetDesktopFolder(&_p) != NOERROR) throw "SHGetDesktopFolder failed";
}
};
Как только мы получили рабочий стол, мы должны создать специальный вид пути, который используется PMDFS. Класс ShPath инкапсулирует этот «путь». Он создан из правильного Unicode пути (используйте mbstowcs, чтобы преобразовать путь ASCII в Unicode: int mbstowcs(wchar_t *wchar, const char *mbchar, size_t count)). Результат преобразования — обобщенный путь относительно рабочего стола. Обратите внимание, что память для нового пути распределена оболочкой – мы инкапсулируем это в SShellPtr, чтобы быть уверенными в правильном освобождении.
class ShPath: public SShellPtr<ITEMIDLIST> {
public:
ShPath (SIfacePtr<IShellFolder>& folder, wchar_t* path) {
ULONG lenParsed = 0;
_hresult = folder->ParseDisplayName(0, 0, path, &lenParsed, &_p, 0);
}
bool IsOK() const {
return SUCCEEDED(_hresult);
}
private:
HRESULT _hresult;
};
Этот путь оболочки станет корнем, из которого окно просмотра начнет его взаимодействие с пользователем.
С точки зрения клиентского кода, окно просмотра — путь, выбранный пользователем. Именно поэтому он наследуется от SShellPtr<ITEMIDLIST>.
Между прочим, ITEMIDLIST — официальное имя для этого обобщенного пути. Это — список, и я не назвал его SHITEMID'ом. Впредь я буду воздерживаться от любых дальнейших комментариев или шуток относительно соглашений, связанных с именами, используемыми в оболочке.
class FolderBrowser: public SShellPtr<ITEMIDLIST> {
public:
FolderBrowser(HWND hwndOwner, SShellPtr<ITEMIDLIST>& root, UINT browseForWhat, char const *title);
char const* GetDisplayName() { return _displayName; }
char const* GetPath() { return _fullPath; }
bool IsOK()const { return _p != 0; };
private:
char _displayName[MAX_PATH];
char _fullPath[MAX_PATH];
BROWSEINFO _browseInfo;
};
FolderBrowser::FolderBrowser(HWND hwndOwner, SShellPtr<ITEMIDLIST>& root, UINT browseForWhat, char const *title) {
_displayName [0] = ' ';
_fullPath [0] = ' ';
_browseInfo.hwndOwner = hwndOwner;
_browseInfo.pidlRoot = root;
_browseInfo.pszDisplayName = _displayName;
_browseInfo.lpszTitle = title;
_browseInfo.ulFlags = browseForWhat;
_browseInfo.lpfn = 0;
_browseInfo.lParam = 0;
_browseInfo.iImage = 0;
// Let the user do the browsing
_p = SHBrowseForFolder(&_browseInfo);
if (_p != 0) SHGetPathFromIDList(_p, _fullPath);
}
Вот так! Разве это не просто?
Далее: Вам, наверное, будет приятно услышать то, что я думаю об OLE?
Что является неправильным в OLE Рассказ посвященного лица
Перевод А. И. Легалова
Англоязычный оригинал находится на сервере компании Reliable Software
Вы могли слышать или читать критические мнения относительно OLE. Программисты обычно жалуются на сложность системы подсчета ссылок и недостатосную поддержку наследования. Microsoft обожествляет этот счетчик, говоря, что нет никакого другого способа, и что этот способ является для вас наилучшим[2]. Интерфейсы, как сказано, должно быть ссылочно подсчитаны (refcounted), и имеется мудрый, рубильник обеспечивающий соединение (агрегацию) частей (нежно называемый ухудшением (aggravation) OLE программистами), который обеспечивает те же самые функциональные возможности, что и наследование. Может быть они правы, и проблема взаимодействия с объектами, загружаемыми во время выполнения настолько сложна, что просто не имеется более лучшего способа? С другой стороны, возможно, что OLE имеет фатальный дефект, который только обостряется во всех других местах.
Фатальный дефект проекта OLE — требование того, чтобы была возможность добраться от любого интерфейса до любого другого интерфейса.
Технически это интерфейсное прыгание сделано за счет того, что каждый интерфейс наследует от матери всех интерфейсов IUnknown. IUnknown имеет фатальный метод QueryInterface, который, как предполагается, возвращает любой интерфейс, обеспечиваемый текущим объектом. Это единственное предположение препятствует наличию любой возможности простой реализации наследования. Позвольте мне объясняют почему.
Предположим, что Вы имеете объект FooObj с интерфейсом IFoo. Эта ситуация легко моделируется в C++ при наличии абстрактного класса (все методы — чистые виртуальные) IFoo и конкретного класс FooObj, который наследуется из IFoo и реализует все его методы.
Теперь Вам вдруг захотелось расширить этот объект, добавляя поддержку для другого интерфейса IBar. В C++ это тривиально, Вы только определяете класс FooBarObj, который наследует от FooObj и IBar. Этот новый класс поддерживает интерфейс IFoo вместе с его реализацией через наследование из FooObj. Он также поддерживает интерфейс IBar и обеспечивает реализацию методов IBar.
Любой, кто знает C++, может сделать это с закрытыми глазами. Так, почему же Вы не можете сделать то же самое в OLE? Здесь проявляется Изъян. Вы должны быть способны получить интерфейс IBar из интерфейса IFoo, используя его QueryInterface. Но, подождите минуту, объект FooObj, который обеспечивает реализацию всех методов IFoo, включая QueryInterface, не имеет никаких сведений относительно IBar! Невозможно было даже представить наличие IBar. Так, как же в этой ситуации обеспечить доступ к IBar?
Хороший вопрос. Я не собираюсь входить в кровавые подробности агрегационной рубки, которая, как предполагается, решает эту проблему. Накладываемые ограничения и пороки начального проекта, действительно, изобретательно, рубят. Так имеется ли лучший проект? Читайте дальше…
Можете ли Вы отметить случай, когда были вынуждены проводить различия между объектом, который реализует интерфейсы и интерфейсами непосредственно? Это два полностью различных понятия. Вы не можете объяснить что-нибудь в OLE без того, чтобы не говорить об объектах, иногда называемых компонентами. Интерфейсы важны, но объекты еще более важны. Когда Вы можете получить один интерфейс от другого? Когда они совместно используют один и тот же основной объект. Вы можете изменять состояние объекта, используя один интерфейс и затем исследовать это состояние через другой интерфейс. Очевидно, что это тот же самый объект! В моем примере я описал два интерфейса, IFoo и IBar, и два объекта (или класса объектов), FooObject и FooBarObject.