![]() |
Технология Клиент-Сервер 2000'1 |
||||||
|
Исходный код ftp.k-press.ru/pub/cs/2000/1/WinScripting.zip
ATL постепенно становится все более популярным в среде программистов, и пример встраивания поддержки скриптов в приложения мы приведем на этой основе. ATL - одна из самых низкоуровневых библиотек С++, и на ее примере хорошо видны подробности процесса. С помощью Microsoft ActiveX Scripting вы можете относительно просто использовать скрипты в ATL-приложениях. В этой статье показано, как создать новое или модифицировать уже имеющееся ATL-приложение для поддержки VBScript.
Создайте новый проект ATL COM EXE с названием AtlClientApp или откройте существующий проект, в который вы хотите вставить поддержку VBScript. Это может быть не-ATL-проект, в этом случае не забудьте добавить необходимые файлы заголовков ATL в ваш проект.
Для создания примера мы воспользуемся Microsoft Developer Studio 6.0.
Создайте новый проект "ATL COM AppWizard" с именем "AtlClientApp." В первом шаге ATL AppWizard выберите "Executable (EXE)" как Server Type и нажмите Finish. Появится диалог New Project Information и сообщит, какие классы будут созданы. Нажмите ОК для создания проекта.
Из меню Insert выберите "New ATL Object". Появится ATL Object Wizard. Выберите Miscellaneous и укажите Dialog, что добавит к проекту Dialog Object. Нажмите Next.
Назовите новый диалог коротким именем "ClientDlg". Остальные имена оставьте присвоенными по умолчанию и нажмите ОК. Диалог должен появиться на экране. Если этого не произошло, откройте закладку "Resource View" в окне WorkSpace. Дважды щелкните по "AtlClientApp Resources" и раскройте дерево ресурсов. Дважды щелкните по Dialog в дереве ресурсов и двойным же щелчком выберите ресурс - диалоговое окно "IDD_CLIENTDLG".
Измените надпись на кнопке ОК на "Run", а "Cancel" замените на "Quit". Оставьте ID кнопок по умолчанию и закройте форму.
Откройте закладку "Class View". Откройте папку "Globals", чтобы увидеть точку входа "_tWinMain", и дважды щелкните по "_tWinMain" для перехода к коду.
Заместите весь код функции на:
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/) { HRESULT hRes = CoInitialize(NULL); ATLASSERT(SUCCEEDED(hRes)); _Module.Init(NULL, hInstance); _Module.dwThreadID = GetCurrentThreadId(); CClientDlg Dlg; int nRet = Dlg.DoModal(); CoUninitialize(); return nRet; }
В начале файла добавьте #include "ClientDlg.h" после других операторов include.
Мы создаем клиентское приложение, поэтому нам не нужно выполнять COM-регистрацию при компиляции EXE-файла. Чтобы пропустить этот шаг, выберите "Settings" из меню Project, перейдите к закладке "Custom Build" и удалите все команды, появляющиеся в окне "Build Commands". Повторите это в окне "Outputs" и нажмите ОК.
Откройте диалог в ResourceView и добавьте текстовое поле, куда пользователь может ввести какой-нибудь VBScript. Щелкните по нему правой кнопкой мыши, и скажите , что это поле "Multiline" и "Want return."
Выберите New ATL Object из меню Insert. Добавьте Simple Object с именем MyObject на закладке Names и выберите Support Connection Points на закладке Attributes.
Добавьте к приложению новый файл с названием MyScriptSite.h и поместите туда следующий код:
// MyScriptSite.h #include <windows.h> #include <activscp.h> class CMyScriptSite : public CComObjectRootEx<CComSingleThreadModel>, public IActiveScriptSite { public: DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CMyScriptSite) COM_INTERFACE_ENTRY(IActiveScriptSite) END_COM_MAP() CMyScriptSite() { } ~CMyScriptSite() { } // Методы IActiveScriptSite... virtual HRESULT __stdcall GetLCID(LCID *plcid) { return S_OK; } virtual HRESULT __stdcall GetItemInfo(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppunkItem, ITypeInfo **ppti) { if(ppti) { *ppti = NULL; // Если просят ITypeInfo... if(dwReturnMask & SCRIPTINFO_ITYPEINFO) { *ppti = m_spTypeInfo; (*ppti)->AddRef(); } } // Если Windows Scripting разместил ppunkItem... if(ppunkItem) { *ppunkItem = NULL; // Если Windows Scripting требует IUnknown... if(dwReturnMask & SCRIPTINFO_IUNKNOWN) { // ...и требуется объект с именем MyObject... USES_CONVERSION; if (!lstrcmpi(_T("MyObject"), OLE2T(pstrName))) { // ...То подсовываем наш объект. *ppunkItem = m_spUnkScriptObject; // ...и AddRef'им наш объект... (*ppunkItem)->AddRef(); } } } return S_OK; } virtual HRESULT __stdcall GetDocVersionString(BSTR *pbstrVersion) { return S_OK; } virtual HRESULT __stdcall OnScriptTerminate( const VARIANT *pvarResult, const EXCEPINFO *pexcepInfo) { return S_OK; } virtual HRESULT __stdcall OnStateChange(SCRIPTSTATE ssScriptState) { return S_OK; } virtual HRESULT __stdcall OnScriptError( IActiveScriptError *pscriptError) { // Это сообщение появится в случае ошибки в скрипте. // Более подробная информация в pscriptError. EXCEPINFO ei; pscriptError->GetExceptionInfo(&ei); USES_CONVERSION; ::MessageBox(::GetActiveWindow(), OLE2T(ei.bstrDescription), "Script Error", MB_SETFOREGROUND); return S_OK; } virtual HRESULT __stdcall OnEnterScript(void) { return S_OK; } virtual HRESULT __stdcall OnLeaveScript(void) { return S_OK; } public: CComPtr<IUnknown> m_spUnkScriptObject; CComPtr<ITypeInfo> m_spTypeInfo; };
На закладке "Class View" раскройте класс "CClientDlg", чтобы видеть членов класса.
Заместите код метода "OnOK" следующим:
LRESULT CClientDlg::OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { // Создаем скрипт-сайт и COM-объекты... // Классы CMyScriptSite и CMyObject не являются полноценными // COM-объектами (у них отсутствует реализация IUnknown). // Чтобы создать на их базе экземпляр COM-объекта, надо // воспользоваться специальным шаблоном - CComObject<>. CComObject<CMyScriptSite> * pMySite = NULL; // Счетчик ссылок у COM-объектов, созданных таким образом // равен нулю. Так что если ему сделать AddRef(), а // затем вызвать Releas(), он умрет!!! CComObject<CMyScriptSite>::CreateInstance(&pMySite); CComObject<CMyObject> * pMyObject = NULL; CComObject<CMyObject>::CreateInstance(&pMyObject); // Инициализируем m_spUnkScriptObject IUnknown-интерфейсом // нашего скрипт-объекта... RET_FAIL(pMyObject->QueryInterface(IID_IUnknown, (void **)&pMySite->m_spUnkScriptObject), "IUnknown initialization"); // Подгружаем и регистрируем библиотеку типов... CComPtr<ITypeLib> sptLib; RET_FAIL(LoadTypeLib(L"AtlClientApp.tlb", &sptLib), "LoadTypeLib"); // Инициализируем реализацию IActiveScriptSite и ITypeInfo... RET_FAIL(sptLib->GetTypeInfoOfGuid(CLSID_MyObject, &pMySite->m_spTypeInfo), "GetTypeInfoOfGuid"); // Создаем экземпляр скрипт-машины, находящейся в VBSCRIPT.DLL. // Если CLSID_VBScript заменить на CLSID другой скрипт-машины, то // можно будет воспользоваться и другим языком... CComPtr<IActiveScript> spAS; RET_FAIL(CoCreateInstance(CLSID_VBScript, NULL, CLSCTX_INPROC_SERVER, IID_IActiveScript, (void **)&spAS), "CoCreateInstance() for CLSID_VBScript"); // Получаем интерфейс IActiveScriptParse. CComPtr<IActiveScriptParse> spASP; RET_FAIL(spAS->QueryInterface(IID_IActiveScriptParse, (void **)&spASP), "QueryInterface() for IID_IActiveScriptParse"); /* Передаем скрипт-машине наш интерфейс IActiveScriptSite... Заметьте что до этого момента счетчик ссылок объекта pMySite (pMySite->m_dwRef) равен нулю. Скрипт-машина, действуя в соответствии с законами COM, увеличивает счетчик ссылок у pMySite на единицу и автоматически становится единственным объектом, имеющим ссылку на pMySite. Так что когда скрипт-машина освободит ссылку на pMySite, он (pMySite) автоматически уничтожится. НЕ ЗАБУДЬТЕ вызвать spAS->Close(), иначе pMySite, а зачастую и MyObject, будут жить вечно ;-). */ RET_FAIL(spAS->SetScriptSite((IActiveScriptSite *)pMySite), "IActiveScript::SetScriptSite()"); // Дадим скрипт-машине шанс инициализироваться... RET_FAIL(spASP->InitNew(), "IActiveScriptParse::InitNew()"); // Добавляем MyObject в пространство имен скрипт-машины... RET_FAIL(spAS->AddNamedItem(OLESTR("MyObject"), SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE), "IActiveScript::AddNamedItem()"); // Получаем текст из текстового окна... TCHAR szBuf[1024]; // Конвертируем его в BSTR... CComBSTR sbsScript(GetDlgItemText(IDC_EDIT1, szBuf, 1024) + 1, szBuf); // Разбираем код скрипта... EXCEPINFO ei; RET_FAIL(spASP->ParseScriptText(sbsScript, NULL/*OLESTR("MyObject")*/, NULL, NULL, 0, 0, 0L, NULL, &ei), "ParseScriptText"); // Задаем состояние машины. Эта строка реально запускает выполнение // скрипта. HRESULT hr = spAS->SetScriptState(SCRIPTSTATE_CONNECTED); if(FAILED(hr)) { spAS->Close(); return 0; } // Оповещаем пользователя, что исполнение скрипта закончено... ::MessageBox(NULL, "Скрипт закончен, щелкните для рассылки события...", "", MB_SETFOREGROUND); // Рассылаем событие... pMyObject->Fire_MyEvent(); // Следующий вызов заставляет скрипт отключиться от событий и // освободить все используемые им объекты. Так как они удерживаются только // скрипт-машиной, то они незамедлительно самоуничтожаются. Можете // проверить это, установив точки прерывания на их деструкторах. spAS->Close(); return 0; }
В начало этого файла (ClientDlg.h), прямо под #include для atlhost.h, поместите:
#include "AtlClientApp.h" #include "MyObject.h" #include "MyScriptSite.h" #define RET_FAIL(hr_param, msg) \ { \ HRESULT hr = hr_param; \ if(FAILED(hr)) \ { \ ATLASSERT(!msg); \ return hr; \ } \ } extern const GUID CLSID_VBScript;
Добавьте следующий код в конец ClientDlg.cpp:
// CLSID скрипт-машины ... #include <initguid.h> DEFINE_GUID(CLSID_VBScript, 0xb54f3741, 0x5b07, 0x11cf, 0xa4, 0xb0, 0x0, 0xaa, 0x0, 0x4a, 0x55, 0xe8);
Щелкните правой кнопкой мыши по интерфейсу IMyObject на закладке ClassView и добавьте методы SayHi и SayHi2, выглядящие следующим образом:
STDMETHODIMP CMyObject::SayHi() { ::MessageBox(NULL, "Мы внутри SayHi()", "CMyObject", MB_SETFOREGROUND); return S_OK; } STDMETHODIMP CMyObject::SayHi2() { ::MessageBox(NULL, " Мы внутри SayHi2()", "CMyObject", MB_SETFOREGROUND); return S_OK; }
Щелкните правой кнопкой мыши по интерфейсу IMyObjectEvents и добавьте метод с именем MyEvent. Прежде, чем перейти к следующему шагу, следует выбрать 'Rebuild All' из меню Build, несмотря на то, что работа еще не завершена. Благодаря этому происходит обновление проекта, и ClassView в следующем шаге опознает ваше событие.
В ClassView щелкните правой кнопкой мыши по классу CMyObject и выберите Implement Connection Point. Отметьте интерфейс IMyObjectEvents и нажмите ОК.
В заключение необходимо исправить ошибку ATL, которую ATL-визард создает при реализации поддержки событий. Для этого откройте MyObject.h и найдите макрос CONNECTION_POINT_ENTRY(IID__IMyObjectEvents). Замените IID__ImyObjectEvents на DIID__IMyObjectEvents.
Выберите Rebuild All из меню Build и запустите пример. Введите следующий VBScript в окно редактирования и нажмите Run:
MyObject.SayHi Sub MyObject_MyEvent MyObject.SayHi2 End Sub
Если вы работаете на VB, Java или другом высокоуровневом языке, примером, приведенным в этой статье, воспользоваться не удастся. Но отчаиваться рано - в поставку Visual Basic6 входит ScriptControl. Он находится в библиотеке Microsoft Script Control. С его помощью можно легко и быстро внедрить поддержку скриптов в приложения VB и Java. Хотя он и не обладает всеми возможностями низкоуровневых интерфейсов, того, что есть, достаточно для большинства применений.
Это, разумеется, самое начало работы с Windows Scripting. Существует масса других COM-интерфейсов, позволяющих заниматься отладкой, узнавать значения переменных и даже определять, как нужно подсвечивать синтаксис того или иного скриптового языка. Но это уже совсем другая история...
Copyright © 1994-2016 ООО "К-Пресс"