> }
>};
Обратите внимание, что базовый класс SActiveDocument — не SObject. Это новый класс DispObject. Он почти подобен SObject с одним лишь различием внутри: вместо использования указателя на IUnknown, он использует указатель на IDispatch. Это имеет значение? Реально — нет, я мог бы использовать SObject и все, работало бы также. За исключением того, что IDISPATCH может использоваться для большего чем запрос только других интерфейсов. Он может использоваться для динамической диспетчеризации вызовов. Так как наша программа написана на C++, и она знает все предоставленные интерфейсы, мы в действительности можем не использовать динамическую диспетчеризацию. Но имеются ситуации, в которых Вы должны дать пользователю возможность решить в реальном времени: какой объект загрузить и какой метод вызвать. Интерпретаторы и языки сценариев позволяют делать это. В частности, Visual Basic, являющийся инструментом для написания сценариев имеет такие функциональные возможности.
Ниже представлена скелетная реализация DispObject, которая демонстрирует эту возможность. Она также объясняет, почему мы говорили о таких «членах», как Visible или FullName при обсуждении интерфейсов. В VB они фактически появляются как элементы данных, или как реквизиты, объектов. Здесь, я реализовал диспетчерский метод GetProperty, который используется, чтобы загрузить значение любого свойства, по его DISPID. И Вы можете получать DISPID любого свойства или метода, если Вы знаете его имя. Метод GetDispId будет делать это для Вас. Подобным способом, Вы можете реализовать и PutProperty, а также Invoke, который может использоваться, чтобы вызвать любой метод по его DISPID. Я оставляю это как упражнение для читателя.
>class DispObject: public CoObject {
>public:
> DispObject(CLSID const& classId) : _iDisp(0) {
> HRESULT hr = ::CoCreateInstance(classId, 0, CLSCTX_ALL, IID_IDispatch, (void**)&_iDisp);
> if (FAILED(hr)) {
> if (hr == E_NOINTERFACE) throw "No IDispatch interface";
> else throw HEx(hr, "Couldn't create DispObject");
> }
> }
> ~DispObject() {
> if (_iDisp) _iDisp->Release();
> }
> operator bool() const { return _iDisp != 0; }
> bool operator!() const { return _iDisp == 0; }
> DISPID GetDispId(WCHAR* funName) {
> DISPID dispid;
> HRESULT hr = _iDisp->GetIDsOfNames(IID_NULL, &funName, 1, GetUserDefaultLCID(), &dispid);
> return dispid;
> }
> void GetProperty(DISPID propId, VARIANT& result) {
> // In parameters
> DISPPARAMS args = { 0, 0, 0, 0 };
> EXCEPINFO except;
> UINT argErr;
> HRESULT hr = _iDisp->Invoke(propId, IID_NULL, GetUserDefaultLCID(), DISPATCH_PROPERTYGET, &args, &result, &except, &argErr);
> if (FAILED (hr)) throw HEx(hr, "Couldn't get property");
> }
> void* AcquireInterface(IID const & iid) {
> void* p = 0;
> HRESULT hr = _iDisp->QueryInterface(iid, &p);
> if (FAILED(hr)) {
> if (hr == E_NOINTERFACE) throw "No such interface";
> else throw HEx(hr, "Couldn't query interface");
> }
> return p;
> }
>protected:
> DispObject(IDispatch * iDisp) : _iDisp(iDisp) {}
> DispObject() : _iDisp(0) {}
>protected:
> IDispatch* _iDisp;
>};
Ниже приводится небольшая иллюстрация динамической диспетчеризации. Конечно, тот же самый результат мог быть получен непосредственно, если вызвать метод get_Name интерфейса IGenericDocument. Мы рассмотрим этот непосредственный метод, использующий таблицу виртуальных фунций через мгновение, чтобы получить полный путь документа.
>// Use docObj as a dispatch interface
>DISPID pid = docObj.GetDispId(L"Name");
>VARIANT varResult;
>::VariantInit(&varResult);
>docObj.GetProperty(pid, varResult);
>BString bName(varResult);
>CString cName(bName);
>canvas.Text(20, y, "Name:");
>canvas.Text(200, y, cName);
Это показывает, как Вы получаете путь, используя таблицу виртуальных функций (vtable).
>SObjFace doc(docObj);
>BString bPath;
>doc->get_FullName(bPath.GetPointer());
Теперь у Вас не должно быть каких-либо проблем при понимании кода, который определяет номер строки, на которой пользователь позиционировал курсор.
>BString bType;
>doc->get_Type(bType.GetPointer());
>if (type.IsEqual("Text")) {