アクティブスクリプトを使用してアプリケーションにスクリプト機能を追加する

最近、スクリプティングなどの乱用のテーマに興味があることに気付きました。 Luaについて、 V8 (Google Chrome JavaScriptエンジン)についての記事がありました。 MicrosoftのActive Scriptingテクノロジ(別名ActiveX Scripting)の使用についてお話したいと思います。
これは、アプリケーションでスクリプトのサポートを実装するために使用される技術です。 これが、みんなのお気に入りのIEブラウザーのJavaScriptエンジンの仕組みです;)ただし、結論に急がないでください。 はい、同じV8エンジンは何倍も高速に動作しますが、このテクノロジーにはその利点と可能なアプリケーションもあります。

アクティブスクリプトの概要


実際、この技術は特に複雑ではありません。 だから、順番に。
まず、このテクノロジーはCOM(コンポーネントオブジェクトモデル)に基づいています。 主なコンポーネントは、 ホストアプリケーションスクリプトエンジンです。

ホストアプリケーションは、スクリプトエンジンに「環境」を提供し、スクリプトが動作できる特定のオブジェクトセットを提供します。
スクリプトエンジンは、特定の言語でのスクリプトの解析、実行、およびデバッグを行います。

モジュールの抽象化は高レベルであり、ホストアプリケーションは、スクリプトの解析と実行のすべての作業がスクリプトエンジンモジュールによって実行されるため、スクリプトに使用される言語さえ気にしません。
一部のエキゾチック言語用に独自のスクリプトエンジンを記述できます。逆の場合も、スクリプトホストモジュールを実装することにより、アプリケーションで既製のエンジン(JScriptまたはVBScript)を使用できます。 この記事は後者に特化しています。

どこで使えますか


アプリケーションがCOMにも基づいている場合、アクティブスクリプトを使用することの最も明確な利点を得ることができます。 この場合、スクリプトからCOMオブジェクトと直接完全に対話できます。 考慮すべき点がいくつかあります(これは主にメソッドの受け入れられた値と戻り値のタイプに関連しています)。
ただし、アプリケーションでCOMを使用しない場合でも、COMオブジェクトの形式で小さな「レイヤー」を実装するだけで十分です。これにより、スクリプトとコードの相互作用が保証されます。

どこから始めますか?


そして、私が始めるための最良の方法は、簡単な例からだと思います。 こちらからダウンロードできます。 さらに、この記事の途中で、コードの一部を引用して、いくつかのポイントを説明します。 この例は、ATLライブラリを使用してC ++で記述されたコンソールアプリケーションです。 この例では、JavaScriptエンジンが使用されています。さらに、記事の本文では、スクリプトエンジンのJScript実装について説明します。 私はJavaScriptが好きで、VBSが嫌いだからです。 それでは、実際の部分に移りましょう。

スクリプトホストの実装


既に述べたように、既製のスクリプトエンジンを使用するには、スクリプトホストモジュールを実装する必要があります。 これは、IActiveScriptSiteおよびIActiveScriptSiteWindowインターフェイスの実装を含む通常のCOMオブジェクトです。 例を複雑にしないために、本格的なCOMオブジェクトを作成せず、IActiveScriptSiteおよびIActiveScriptSiteWindowから継承された通常のC ++クラスを管理しました。

class CMyScriptHost : public IActiveScriptSite,
public IActiveScriptSiteWindow


* This source code was highlighted with Source Code Highlighter .


すべてのCOMオブジェクトに共通のIUnknownインターフェイスを実装することから始めましょう(クラスはIActiveScriptSiteおよびIActiveScriptSiteWindowインターフェイスから間接的に継承しました)。 複雑なことはなく、3つの方法があります。

STDMETHOD(QueryInterface)(REFIID riid, void * * ppvObj);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();


* This source code was highlighted with Source Code Highlighter .


AddRefは参照カウンターを増やします。 Rlease-カウンターが0になったときにオブジェクトを減らし、削除します。 QueryInterfaceは、サポートされているインターフェイスの1つが要求された場合、オブジェクトへのポインターを返します。

IActiveScriptSiteインターフェイスの実装では、至る所にスタブがあります

STDMETHOD(GetLCID)(
LCID *plcid ); // address of variable for language identifier
STDMETHOD(GetItemInfo)(
LPCOLESTR pstrName, // address of item name
DWORD dwReturnMask, // bit mask for information retrieval
IUnknown **ppunkItem, // address of pointer to item's IUnknown
ITypeInfo **ppTypeInfo); // address of pointer to item's ITypeInfo
STDMETHOD(GetDocVersionString)(
BSTR *pbstrVersionString); // address of document version string
STDMETHOD(OnScriptTerminate)(
const VARIANT *pvarResult, // address of script results
const EXCEPINFO *pexcepinfo); // address of structure with exception information
STDMETHOD(OnStateChange)(
SCRIPTSTATE ssScriptState); // new state of engine
STDMETHOD(OnScriptError)(
IActiveScriptError *pase); // address of error interface
STDMETHOD(OnEnterScript)( void );
STDMETHOD(OnLeaveScript)( void );


* This source code was highlighted with Source Code Highlighter .


例外はOnScriptErrorメソッドのみです。エラー情報を含む行が生成され、MessageBoxを使用して表示されます。

STDMETHODIMP CMyScriptHost::OnScriptError(IActiveScriptError *pase)
{
#ifdef _DEBUG
EXCEPINFO Exception;
HRESULT hr = pase->GetExceptionInfo(&Exception);
if (SUCCEEDED(hr))
{
CString sErrLog = _T( "" );
sErrLog += _T( "EXCEPINFO" );
sErrLog += _T( "\n\rDescription: " );
sErrLog += Exception.bstrDescription;
sErrLog += _T( "\n\rSource: " );
sErrLog += Exception.bstrSource;

CComBSTR bsSrcLineText;
hr = pase->GetSourceLineText(&bsSrcLineText);
if (SUCCEEDED(hr))
{
sErrLog += _T( "\n\rSource line text: " );
sErrLog += bsSrcLineText;
}

DWORD dwSourceContext = 0;
ULONG ulLineNumber = 0;
LONG lCharacterPosition = 0;

hr = pase->GetSourcePosition(&dwSourceContext, &ulLineNumber, &lCharacterPosition);
if (SUCCEEDED(hr))
{
CString sSourceContext;
sErrLog += _T( "\n\rSource context: " );
sSourceContext.Format(_T( "%d" ), dwSourceContext);
sErrLog += sSourceContext;

CString sLineNumber;
sErrLog += _T( "\n\rLine number: " );
sLineNumber.Format(_T( "%d" ), ulLineNumber);
sErrLog += sLineNumber;

CString sCharPos;
sErrLog += _T( "\n\rCharacterPosition: " );
sCharPos.Format(_T( "%d" ), lCharacterPosition);
sErrLog += sCharPos;
}

::MessageBox(0, sErrLog, COLE2T(Exception.bstrSource), 0);
}
#endif // _DEBUG

return S_OK;
}


* This source code was highlighted with Source Code Highlighter .


IActiveScriptSiteWindowの実装もスタブです。

次に、スクリプトエンジンオブジェクトへのポインターを格納するための2つのフィールドをクラスに追加します。

CComPtr<IActiveScript> m_pEngine; // reference to the scripting engine <br>CComQIPtr<IActiveScriptParse> m_pParser; // reference to the IActiveScriptParse interface of the scripting engine <br><br> * This source code was highlighted with Source Code Highlighter .


実際、これらの変数は同じオブジェクトの異なるインターフェースを指します。

次に、クラスにいくつかのメソッドを追加します。

HRESULT Initialize();<br>HRESULT Close();<br>HRESULT PutScript(CString sScriptText);<br>HRESULT CallJSFunction(CString sFuncName, VARIANT *varResult); <br><br> * This source code was highlighted with Source Code Highlighter .


おそらくご想像のとおり、Initialize()メソッドはスクリプトホストを初期化します。

HRESULT CMyScriptHost::Initialize()<br>{<br> HRESULT hr = E_FAIL;<br><br> //First, create the scripting engine with a call to CoCreateInstance, <br> //placing the created engine in m_Engine. <br><br> hr = m_pEngine.CoCreateInstance(CComBSTR(_T( "JScript" )));<br> if (SUCCEEDED(hr) && m_pEngine)<br> {<br> m_pParser = m_pEngine;<br> if (m_pParser)<br> {<br> //The engine needs to know the host it runs on. <br> hr = m_pEngine->SetScriptSite( this );<br> ATLASSERT(SUCCEEDED(hr));<br><br> //Initialize the script engine so it's ready to run. <br> hr = m_pParser->InitNew();<br> ATLASSERT(SUCCEEDED(hr));<br> }<br> }<br><br> return hr;<br>} <br><br> * This source code was highlighted with Source Code Highlighter .


最初に、スクリプトエンジンオブジェクトを作成し、m_pEngineに保存します。 次に、同じオブジェクトのIActiveScriptParseインターフェイスへのポインターを取得し、m_pParserに保存します(ATLに不慣れな場合、インターフェイスを非表示にします。これは、値を割り当てるときに適切なインターフェイスを取得するCComQIPtrインターフェイスへのスマートポインターを使用するためです)。 次に、そのサイト(つまり、ホスト)のエンジン-それ自体をインストールします。 スクリプトエンジンを初期化します。

Close()メソッドにより、スクリプトが正しく完了します。

HRESULT CMyScriptHost::Close()
{
HRESULT hr = E_FAIL;

if (m_pEngine)
{
if (m_pParser)
m_pParser.Release();

// Disconnect the host application from the engine. This will prevent the
// further firing of events. Event sinks that are in progress are
// completed before the state changes.
m_pEngine->SetScriptState(SCRIPTSTATE_DISCONNECTED);

// Call to InterruptScriptThread to abandon any running scripts and force
// a cleanup of all script elements.
m_pEngine->InterruptScriptThread(SCRIPTTHREADID_ALL, NULL, 0 );
m_pEngine->Close();

m_pEngine.Release();

hr = S_OK;
}

return hr;
}


* This source code was highlighted with Source Code Highlighter .


スクリプトテキストに渡されたPutScript()メソッドは、パーサーにフィードし、SetScriptState(SCRIPTSTATE_CONNECTED)を呼び出してエンジンを開始します。

HRESULT CMyScriptHost::PutScript( CString sScriptText )
{
HRESULT hr = E_FAIL;

//Pass the script to be run to the script engine with a call to ParseScriptText
hr = m_pParser->ParseScriptText(sScriptText, NULL, NULL, NULL, 0, 0, 0, NULL, NULL);
hr = m_pEngine->SetScriptState(SCRIPTSTATE_CONNECTED);

return hr;
}


* This source code was highlighted with Source Code Highlighter .


最後に、CallJSFunction()メソッドは、指定された名前で関数を呼び出し、結果をVARIANT型の変数として返します。

HRESULT CMyScriptHost::CallJSFunction( CString sFuncName, VARIANT *varResult )
{
HRESULT hr;
CComPtr<IDispatch> pDispScript;

hr = m_pEngine->GetScriptDispatch( NULL, &pDispScript );

if ( SUCCEEDED(hr) && pDispScript )
{
hr = pDispScript.Invoke0(sFuncName, varResult);
}

return hr;
}


* This source code was highlighted with Source Code Highlighter .


現在JavaScript関数にパラメーターは渡されていませんが、これは非常に簡単です。Invoke0メソッドの代わりにInvoke1、Invoke2、またはInvokeNを使用し、VARIANT型の変数にパラメーターを渡します。

以上で、簡単なスクリプトを実行するために必要な最小限の準備が整いました。

Hello World!



int _tmain( int argc, _TCHAR* argv[])
{
CoInitialize(NULL);

CMyScriptHost* myScriptHost = new CMyScriptHost(); // Script host`
myScriptHost->AddRef();

HRESULT hr = E_FAIL;

hr = myScriptHost->Initialize();
if (SUCCEEDED(hr))
{
// test
// - "Hello, World!"
CString sScriptText = _T( "function test() { \
return 'Hello, World!'; \
}"
);

hr = myScriptHost->PutScript(sScriptText);
if (SUCCEEDED(hr))
{
CComVariant varResult; //
hr = myScriptHost->CallJSFunction(_T( "test" ), &varResult); // test
if (SUCCEEDED(hr))
{
_tprintf(_T( "Result: %s\n\r" ), COLE2T(varResult.bstrVal)); //
_tprintf(_T( "\n\rPress any key to exit..." ));
_gettch();
}
}

myScriptHost->Close(); //
}

myScriptHost->Release();

CoUninitialize();
return 0;
}


* This source code was highlighted with Source Code Highlighter .


コンパイルして実行します。 コンソールに表示されます:

結果:こんにちは、World!

終了するには任意のキーを押してください...

おそらく初めてで十分です。 次の記事では、スクリプトとコンパイル済みコードのより深い相互作用について説明します。

PS CMyScriptHostクラスでは、この例は部分的にzsergコードを使用します-プログラミングのすべてとすべての人の第一人者:)
PPSHabréについての私の最初の記事、健全な批判と願いを歓迎します
PPPSパーサーは、スクリプトという言葉を小さな文字でのみ書いているどこでも、本当に好きではないようです。 サンプルコードの正しいスペルを参照してください。

Source: https://habr.com/ru/post/J74548/


All Articles