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] = '\0';
_fullPath [0] = '\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.
Фактически, любой, кто реализует интерфейсы (используя C++, C, или Basic) имеет дело с объектами. Однако, эта очень важная абстракция полностью отсутствует с точки зрения клиента OLE. Все, что клиент видит – это интерфейсы. Основной объект подобен призраку.
Но это не призрак. Он физически представлен в адресном пространстве вашей программы, или непосредственно, или как заглушка пересылки. Так, почему скрывают это? Действительно, разве OLE не было бы более простым с явным понятием объекта? Давайте посмотрим, как это работало бы.
2
Меня задело за живое введение к главе по агрегации в прекрасной (хотя так или иначе тупой) книге "Внутри COM" Дейла Роджерсона ("Inside COM" by Dale Rogerson). Если бы он знал реальную историю, он не был бы столь непреклонен в его защите агрегирования.